Extend security state
Implement management of:
- CSRF protection
- Content Security Policy
- Agent to Master security
Closes-PROD: https://mirantis.jira.com/browse/PROD-20183
Change-Id: I09439bbe534b84ad760091b7db471b5c07274a76
diff --git a/README.rst b/README.rst
index fc3c109..88e9eff 100644
--- a/README.rst
+++ b/README.rst
@@ -2,8 +2,8 @@
Jenkins formula
===============
-Jenkins CI is an open source automation server written in Java. Jenkins
-helps to automate the non-human part of software development process, with
+Jenkins CI is an open source automation server written in Java. Jenkins
+helps to automate the non-human part of software development process, with
continuous integration and facilitating technical aspects of continuous delivery.
(*Source*: `Wikipedia <https://en.wikipedia.org/wiki/Jenkins_(software)>`_ )
@@ -509,7 +509,7 @@
security:
matrix:
# set true for use ProjectMatrixAuthStrategy instead of GlobalMatrixAuthStrategy
- project_based: false
+ project_based: false
permissions:
Jenkins:
# administrator access
@@ -521,14 +521,14 @@
- user1
- user2
# agents permissions
- MasterComputer:
- BUILD:
+ MasterComputer:
+ BUILD:
- user3
# jobs permissions
- hudson:
+ hudson:
model:
Item:
- BUILD:
+ BUILD:
- user4
`Common matrix strategies <https://github.com/arbabnazar/configuration/blob/c08a5eaf4e04a68d2481375502a926517097b253/playbooks/roles/tools_jenkins/templates/projectBasedMatrixSecurity.groovy.j2>`_
@@ -697,7 +697,7 @@
Slack plugin configuration
.. code-block:: yaml
-
+
jenkins:
client:
slack:
@@ -735,15 +735,15 @@
Jenkins Global env properties enforcing
- .. code-block:: yaml
+.. code-block:: yaml
- jenkins:
- client:
- globalenvprop:
- OFFLINE_DEPLOYMENT:
- enabled: true
- name: "OFFLINE_DEPLOYMENT" # optional, default using dict key
- value: "true"
+ jenkins:
+ client:
+ globalenvprop:
+ OFFLINE_DEPLOYMENT:
+ enabled: true
+ name: "OFFLINE_DEPLOYMENT" # optional, default using dict key
+ value: "true"
Throttle categories management from client (requires
`Throttle Concurrent Builds <https://plugins.jenkins.io/throttle-concurrents>`_
@@ -825,6 +825,40 @@
authkey: |
SOMESSHKEY
+CSRF Protection configuration
+
+.. code-block:: yaml
+
+ jenkins:
+ client:
+ security:
+ csrf:
+ enabled: true
+ proxy_compat: false
+
+
+Agent to Master Access Control
+
+.. code-block:: yaml
+
+ jenkins:
+ client:
+ security:
+ agent2master:
+ enabled: true
+ whitelisted: ''
+ file_path_rules: ''
+
+Content Security Policy configuration
+
+.. code-block:: yaml
+
+ jenkins:
+ client:
+ security:
+ csp: "sandbox; default-src 'none'; img-src 'self'; style-src 'self';"
+
+
Usage
=====
diff --git a/_states/jenkins_security.py b/_states/jenkins_security.py
index e8389ba..ae4a496 100644
--- a/_states/jenkins_security.py
+++ b/_states/jenkins_security.py
@@ -14,6 +14,66 @@
return True
+def agent2master(name, enabled, whitelisted, file_path_rules):
+ """
+ Jenkins Agent to Master Access Control state method
+
+ :param enabled
+ :param whitelisted: Whitelisted Commands
+ :param file_path_rules: File Access Rules
+ """
+
+ template = __salt__['jenkins_common.load_template'](
+ 'salt://jenkins/files/groovy/security.agent2master.template',
+ __env__)
+ return __salt__['jenkins_common.api_call'](name, template,
+ ["CHANGED", "EXISTS"],
+ {
+ 'enabled': enabled,
+ 'newWhitelistedContent': whitelisted,
+ 'newFilePathRulesContent': file_path_rules
+ },
+ 'Agent to Master Access Control')
+
+def csp(name, policy):
+ """
+ Jenkins Content Security Policy state method
+
+ :param policy
+ """
+ default_policy = """\
+ sandbox; default-src 'none'; img-src 'self'; style-src 'self';
+ """.strip()
+
+ template = __salt__['jenkins_common.load_template'](
+ 'salt://jenkins/files/groovy/security.csp.template',
+ __env__)
+ return __salt__['jenkins_common.api_call'](name, template,
+ ["CHANGED", "EXISTS"],
+ {
+ 'policy': policy if policy else default_policy,
+ },
+ 'Content Security Policy')
+
+def csrf(name, enabled, proxy_compat):
+ """
+ Jenkins CSRF protection state method
+
+ :param enabled
+ :param proxy_compat
+ """
+
+ template = __salt__['jenkins_common.load_template'](
+ 'salt://jenkins/files/groovy/security.csrf.template',
+ __env__)
+ return __salt__['jenkins_common.api_call'](name, template,
+ ["CHANGED", "EXISTS"],
+ {
+ 'csrfEnabled': enabled,
+ 'proxyCompat': proxy_compat
+ },
+ 'CSRF Protection')
+
def ldap(name, server, root_dn, user_search_base, manager_dn, manager_password,
user_search="", group_search_base="", inhibit_infer_root_dn=False):
"""
diff --git a/jenkins/client/security.sls b/jenkins/client/security.sls
index 18bba8f..4b9cc41 100644
--- a/jenkins/client/security.sls
+++ b/jenkins/client/security.sls
@@ -17,4 +17,28 @@
jenkins_security.matrix:
- strategies: {{ client.security.matrix.permissions }}
- project_based: {{ client.security.matrix.get('project_based', False) }}
-{%- endif %}
\ No newline at end of file
+{%- endif %}
+
+{%- if client.security.csrf is defined %}
+jenkins_csrf_protection:
+ jenkins_security.csrf:
+ - enabled: {{ client.security.csrf.get('enabled', False) }}
+ - proxy_compat: {{ client.security.csrf.get('proxy_compat', False) }}
+{%- endif %}
+
+{%- if client.security.csp is defined %}
+jenkins_content_security_policy:
+ jenkins_security.csp:
+ - policy: {{ client.security.csp }}
+{%- endif %}
+
+{%- if client.security.agent2master is defined %}
+jenkins_agent2master_security:
+ jenkins_security.agent2master:
+ - enabled: {{ client.security.agent2master.get('enabled', False) }}
+ - whitelisted: |
+ {{ client.security.agent2master.get('whitelisted', '')|indent(8) }}
+ - file_path_rules: |
+ {{ client.security.agent2master.get('file_path_rules', '')|indent(8) }}
+{%- endif %}
+
diff --git a/jenkins/files/groovy/security.agent2master.template b/jenkins/files/groovy/security.agent2master.template
new file mode 100644
index 0000000..b1f7b2c
--- /dev/null
+++ b/jenkins/files/groovy/security.agent2master.template
@@ -0,0 +1,52 @@
+#!groovy
+import jenkins.model.Jenkins
+import jenkins.security.s2m.*
+
+Boolean enabled = "${enabled}".toBoolean()
+
+String newWhitelistedContent="""\
+${newWhitelistedContent}"""
+
+String newFilePathRulesContent="""\
+${newFilePathRulesContent}"""
+
+Boolean changed = false
+
+/*
+ * MasterKillSwitch has reversed logic
+ * `true` means `disabled` and vise versa
+ */
+
+def instance = Jenkins.getInstance()
+def whitelistRule = instance.injector.getInstance(AdminWhitelistRule.class)
+Boolean s2mState = !whitelistRule.getMasterKillSwitch() //reversed logic. `true` means disabled
+
+if ( s2mState != enabled ) {
+ whitelistRule.setMasterKillSwitch(!enabled)
+ changed = true
+}
+
+String whitelistedFile = whitelistRule.whitelisted.toString()
+String filePathRulesFile = whitelistRule.filePathRules.toString()
+String whitelistedContent = new File(whitelistedFile).getText('UTF-8')
+String filePathRulesContent = new File(filePathRulesFile).getText('UTF-8')
+
+if (!newWhitelistedContent.endsWith("\n"))
+ newWhitelistedContent+="\n"
+
+if (newWhitelistedContent != whitelistedContent) {
+ whitelistRule.whitelisted.set(newWhitelistedContent);
+ changed = true
+}
+
+if (newFilePathRulesContent != filePathRulesContent) {
+ whitelistRule.filePathRules.parseTest(newFilePathRulesContent)
+ whitelistRule.filePathRules.set(newFilePathRulesContent);
+ changed = true
+}
+
+if (changed) {
+ print "CHANGED"
+} else {
+ print "EXISTS"
+}
diff --git a/jenkins/files/groovy/security.csp.template b/jenkins/files/groovy/security.csp.template
new file mode 100644
index 0000000..51c794c
--- /dev/null
+++ b/jenkins/files/groovy/security.csp.template
@@ -0,0 +1,10 @@
+#!groovy
+
+def currentPolicy = System.getProperty("hudson.model.DirectoryBrowserSupport.CSP")
+def newPolicy = """${policy}""".trim()
+if ( currentPolicy != newPolicy ){
+ System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", newPolicy)
+ print ("CHANGED")
+} else {
+ print ("EXISTS")
+}
diff --git a/jenkins/files/groovy/security.csrf.template b/jenkins/files/groovy/security.csrf.template
new file mode 100644
index 0000000..4d8ef1f
--- /dev/null
+++ b/jenkins/files/groovy/security.csrf.template
@@ -0,0 +1,34 @@
+#!groovy
+
+import hudson.security.csrf.DefaultCrumbIssuer
+import jenkins.model.Jenkins
+
+Boolean proxyCompat = "${proxyCompat}".toBoolean()
+Boolean csrfEnabled = "${csrfEnabled}".toBoolean()
+
+def instance = Jenkins.getInstance()
+def issuer = instance.getCrumbIssuer()
+
+if (csrfEnabled) {
+ if (! issuer ) {
+ instance.setCrumbIssuer(new DefaultCrumbIssuer(proxyCompat))
+ instance.save()
+ print "CHANGED"
+ } else {
+ if ( issuer.excludeClientIPFromCrumb != proxyCompat ) {
+ issuer.excludeClientIPFromCrumb = proxyCompat
+ instance.save()
+ print "CHANGED"
+ } else {
+ print "EXISTS"
+ }
+ }
+} else {
+ if ( issuer ) {
+ instance.setCrumbIssuer(null)
+ instance.save()
+ print "CHANGED"
+ } else {
+ print "EXISTS"
+ }
+}