mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-20 05:19:40 +02:00
feat(dockerExecute): Infer Kubernetes securityContext from dockerOptions (#4557)
* Allow running as different user on Kubernetes Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com> Co-authored-by: Johannes Dillmann <j.dillmann@sap.com> Co-authored-by: Pavel Busko <pavel.busko@sap.com> * infer securityContext from dockerOptions Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com> Co-authored-by: Pavel Busko <pavel.busko@sap.com> * verify --user flag value --------- Co-authored-by: Johannes Dillmann <j.dillmann@sap.com> Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com> Co-authored-by: Anil Keshav <anil.keshav@sap.com>
This commit is contained in:
parent
e38ee67748
commit
caee8db407
@ -143,6 +143,101 @@ class DockerExecuteTest extends BasePiperTest {
|
||||
assertTrue(bodyExecuted)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteInsidePodWithCustomUserShort() throws Exception {
|
||||
Map kubernetesConfig = [:]
|
||||
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body ->
|
||||
kubernetesConfig = config
|
||||
return body()
|
||||
})
|
||||
binding.setVariable('env', [ON_K8S: 'true'])
|
||||
stepRule.step.dockerExecute(
|
||||
script: nullScript,
|
||||
dockerImage: 'maven:3.5-jdk-8-alpine',
|
||||
dockerOptions: ["-u 0:0", "-v foo:bar"]
|
||||
) {
|
||||
bodyExecuted = true
|
||||
}
|
||||
|
||||
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
|
||||
assertThat(kubernetesConfig.securityContext, is([
|
||||
'runAsUser': 0,
|
||||
'runAsGroup': 0
|
||||
]))
|
||||
assertTrue(bodyExecuted)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteInsidePodWithCustomUserLong() throws Exception {
|
||||
Map kubernetesConfig = [:]
|
||||
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body ->
|
||||
kubernetesConfig = config
|
||||
return body()
|
||||
})
|
||||
binding.setVariable('env', [ON_K8S: 'true'])
|
||||
stepRule.step.dockerExecute(
|
||||
script: nullScript,
|
||||
dockerImage: 'maven:3.5-jdk-8-alpine',
|
||||
dockerOptions: ["--user 0:0", "-v foo:bar"]
|
||||
) {
|
||||
bodyExecuted = true
|
||||
}
|
||||
|
||||
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
|
||||
assertThat(kubernetesConfig.securityContext, is([
|
||||
'runAsUser': 0,
|
||||
'runAsGroup': 0
|
||||
]))
|
||||
assertTrue(bodyExecuted)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteInsidePodWithCustomUserNoGroup() throws Exception {
|
||||
Map kubernetesConfig = [:]
|
||||
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body ->
|
||||
kubernetesConfig = config
|
||||
return body()
|
||||
})
|
||||
binding.setVariable('env', [ON_K8S: 'true'])
|
||||
stepRule.step.dockerExecute(
|
||||
script: nullScript,
|
||||
dockerImage: 'maven:3.5-jdk-8-alpine',
|
||||
dockerOptions: ["-v foo:bar", "-u 0"]
|
||||
) {
|
||||
bodyExecuted = true
|
||||
}
|
||||
|
||||
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
|
||||
assertThat(kubernetesConfig.securityContext, is([
|
||||
'runAsUser': 0
|
||||
]))
|
||||
assertTrue(bodyExecuted)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteInsidePodWithCustomUserGroupString() throws Exception {
|
||||
Map kubernetesConfig = [:]
|
||||
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body ->
|
||||
kubernetesConfig = config
|
||||
return body()
|
||||
})
|
||||
binding.setVariable('env', [ON_K8S: 'true'])
|
||||
stepRule.step.dockerExecute(
|
||||
script: nullScript,
|
||||
dockerImage: 'maven:3.5-jdk-8-alpine',
|
||||
dockerOptions: ["-v foo:bar", "-u root:wheel"]
|
||||
) {
|
||||
bodyExecuted = true
|
||||
}
|
||||
|
||||
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
|
||||
assertThat(kubernetesConfig.securityContext, is([
|
||||
'runAsUser': 'root',
|
||||
'runAsGroup': 'wheel'
|
||||
]))
|
||||
assertTrue(bodyExecuted)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteInsideDockerContainer() throws Exception {
|
||||
stepRule.step.dockerExecute(script: nullScript, dockerImage: 'maven:3.5-jdk-8-alpine') {
|
||||
|
@ -181,6 +181,8 @@ void call(Map parameters = [:], body) {
|
||||
config.dockerEnvVars?.each { key, value ->
|
||||
dockerEnvVars << "$key=$value"
|
||||
}
|
||||
|
||||
def securityContext = securityContextFromOptions(config.dockerOptions)
|
||||
if (env.POD_NAME && isContainerDefined(config)) {
|
||||
container(getContainerDefined(config)) {
|
||||
withEnv(dockerEnvVars) {
|
||||
@ -205,6 +207,7 @@ void call(Map parameters = [:], body) {
|
||||
dockerWorkspace: config.dockerWorkspace,
|
||||
stashContent: config.stashContent,
|
||||
stashNoDefaultExcludes: config.stashNoDefaultExcludes,
|
||||
securityContext: securityContext,
|
||||
]
|
||||
|
||||
if (config.sidecarImage) {
|
||||
@ -217,6 +220,7 @@ void call(Map parameters = [:], body) {
|
||||
sidecarEnvVars: parameters.sidecarEnvVars,
|
||||
]
|
||||
}
|
||||
|
||||
dockerExecuteOnKubernetes(dockerExecuteOnKubernetesParams) {
|
||||
echo "[INFO][${STEP_NAME}] Executing inside a Kubernetes Pod"
|
||||
body()
|
||||
@ -340,20 +344,40 @@ private getDockerOptions(Map dockerEnvVars, Map dockerVolumeBind, def dockerOpti
|
||||
}
|
||||
|
||||
if (dockerOptions) {
|
||||
if (dockerOptions instanceof CharSequence) {
|
||||
dockerOptions = [dockerOptions]
|
||||
}
|
||||
if (dockerOptions instanceof List) {
|
||||
dockerOptions.each { String option ->
|
||||
options << escapeBlanks(option)
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unexpected type for dockerOptions. Expected was either a list or a string. Actual type was: '${dockerOptions.getClass()}'")
|
||||
}
|
||||
options.addAll(dockerOptionsToList(dockerOptions))
|
||||
}
|
||||
|
||||
return options.join(' ')
|
||||
}
|
||||
|
||||
@NonCPS
|
||||
def securityContextFromOptions(dockerOptions) {
|
||||
Map securityContext = [:]
|
||||
|
||||
if (!dockerOptions) {
|
||||
return null
|
||||
}
|
||||
|
||||
def userOption = dockerOptionsToList(dockerOptions).find { (it.startsWith("-u ") || it.startsWith("--user ")) }
|
||||
if (!userOption) {
|
||||
return null
|
||||
}
|
||||
|
||||
def userOptionParts = userOption.split(" ")
|
||||
if (userOptionParts.size() != 2) {
|
||||
throw new IllegalArgumentException("Unexpected --user flag value in dockerOptions '${userOption}'")
|
||||
}
|
||||
|
||||
def userGroupIds = userOptionParts[1].split(":")
|
||||
|
||||
securityContext.runAsUser = userGroupIds[0].isInteger() ? userGroupIds[0].toInteger() : userGroupIds[0]
|
||||
|
||||
if (userGroupIds.size() == 2) {
|
||||
securityContext.runAsGroup = userGroupIds[1].isInteger() ? userGroupIds[1].toInteger() : userGroupIds[1]
|
||||
}
|
||||
|
||||
return securityContext
|
||||
}
|
||||
|
||||
boolean isContainerDefined(config) {
|
||||
Map containerMap = ContainerMap.instance.getMap()
|
||||
@ -383,6 +407,28 @@ boolean isKubernetes() {
|
||||
return Boolean.valueOf(env.ON_K8S)
|
||||
}
|
||||
|
||||
@NonCPS
|
||||
def dockerOptionsToList(dockerOptions) {
|
||||
def options = []
|
||||
if (!dockerOptions) {
|
||||
return options
|
||||
}
|
||||
|
||||
if (dockerOptions instanceof CharSequence) {
|
||||
dockerOptions = [dockerOptions]
|
||||
}
|
||||
|
||||
if (dockerOptions instanceof List) {
|
||||
dockerOptions.each { String option ->
|
||||
options << escapeBlanks(option)
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unexpected type for dockerOptions. Expected was either a list or a string. Actual type was: '${dockerOptions.getClass()}'")
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
/*
|
||||
* Escapes blanks for values in key/value pairs
|
||||
* E.g. <code>description=Lorem ipsum</code> is
|
||||
|
Loading…
x
Reference in New Issue
Block a user