From b92aef33ed30f898b748d9ab0bf6c332a4516080 Mon Sep 17 00:00:00 2001 From: Mike Bonnet Date: Oct 28 2019 15:06:41 +0000 Subject: [PATCH 1/2] krb5: deploy, authenticate with, and manage a Kerberos 5 KDC The krb5 variable supports deploying a new containerized Kerberos 5 KDC (using the krb5-fedora image from https://pagure.io/krb5-fedora and quay.io/factory2/krb5-fedora). It provides methods for running admin commands (adding user and service principals), authenticating to the KDC, and running commands in an environment with a valid Kerberos ccache. It can be used to configure services for Kerberos authentication and validate their correct operation. --- diff --git a/resources/openshift/templates/krb5.yaml b/resources/openshift/templates/krb5.yaml new file mode 100644 index 0000000..1b12c65 --- /dev/null +++ b/resources/openshift/templates/krb5.yaml @@ -0,0 +1,262 @@ +--- +apiVersion: v1 +kind: Template +metadata: + name: krb5-test-template +labels: + template: krb5-test-template +parameters: +- name: TEST_ID + displayName: Test id + description: Short unique identifier for this test run (e.g. Jenkins job number) + required: true +- name: NAME + displayName: The name for this deployment config. + required: true + value: krb5 +- name: REALM + displayName: The Kerberos realm to manage. + required: true + value: CLUSTER.LOCAL +- name: DOMAIN + displayName: The DNS domain associated with the realm. + required: true + value: cluster.local +- name: KDC_DB_PASSWORD + displayName: The master password for the Kerberos database. + generate: expression + from: "[\\w]{16}" +- name: ADMIN_PASSWORD + displayName: The password for the kadmin/admin principal. + generate: expression + from: "[\\w]{16}" +- name: INIT_USERS + displayName: A comma-separated list of initial users to define, in username:password format. + required: false +- name: IMAGE + displayName: Location of the image to deploy. + required: true + value: quay.io/factory2/krb5-fedora:latest +objects: +- apiVersion: v1 + kind: Secret + metadata: + name: ${NAME}-${TEST_ID}-secret + labels: + app: ${NAME} + service: kerberos + environment: test-${TEST_ID} + stringData: + KDC_DB_PASSWORD: ${KDC_DB_PASSWORD} + ADMIN_PASSWORD: ${ADMIN_PASSWORD} +- apiVersion: v1 + kind: ConfigMap + metadata: + name: ${NAME}-${TEST_ID}-config + labels: + app: ${NAME} + service: kerberos + environment: test-${TEST_ID} + data: + krb5.conf: | + includedir /etc/krb5.conf.d/ + + [logging] + default = STDERR + kdc = STDERR + admin_server = STDERR + debug = true + + [libdefaults] + dns_lookup_kdc = false + dns_lookup_realm = false + dns_canonicalize_hostname = false + ticket_lifetime = 24h + renew_lifetime = 7d + forwardable = true + pkinit_anchors = FILE:/etc/pki/tls/certs/ca-bundle.crt + spake_preauth_groups = edwards25519 + default_realm = ${REALM} + default_ccache_name = FILE:/tmp/%{uid}-ccache + + [realms] + ${REALM} = { + kdc = kerberos-${TEST_ID}:8088 + admin_server = kerberos-${TEST_ID}:8749 + kpasswd_server = kerberos-${TEST_ID}:8464 + kdc_listen = 8088 + kdc_tcp_listen = 8088 + kadmind_listen = 8749 + kpasswd_listen = 8464 + acl_file = /etc/kadm5.acl + } + + [domain_realm] + .${DOMAIN} = ${REALM} + ${DOMAIN} = ${REALM} + kadm5.acl: | + */admin@${REALM} * +- apiVersion: v1 + kind: DeploymentConfig + metadata: + name: ${NAME}-${TEST_ID} + labels: + app: ${NAME} + service: kerberos + environment: test-${TEST_ID} + spec: + replicas: 1 + strategy: + type: Recreate + selector: + app: ${NAME} + service: kerberos + environment: test-${TEST_ID} + template: + metadata: + labels: + app: ${NAME} + service: kerberos + environment: test-${TEST_ID} + spec: + initContainers: + - name: init-kdc-db + image: ${IMAGE} + imagePullPolicy: Always + command: + - /usr/local/bin/init-kdc-db + env: + - name: REALM + value: ${REALM} + - name: INIT_USERS + value: ${INIT_USERS} + envFrom: + - secretRef: + name: ${NAME}-${TEST_ID}-secret + volumeMounts: + - name: config-vol + mountPath: /etc/krb5.conf + subPath: krb5.conf + - name: config-vol + mountPath: /etc/kadm5.acl + subPath: kadm5.acl + - name: data-vol + mountPath: /var/kerberos/krb5kdc + resources: + requests: + memory: "384Mi" + cpu: "300m" + limits: + memory: "512Mi" + cpu: "500m" + containers: + - name: kdc + image: ${IMAGE} + imagePullPolicy: Always + command: + - /usr/sbin/krb5kdc + - -n + volumeMounts: + - name: config-vol + subPath: krb5.conf + mountPath: /etc/krb5.conf + - name: config-vol + subPath: kadm5.acl + mountPath: /etc/kadm5.acl + - name: data-vol + mountPath: /var/kerberos/krb5kdc + ports: + - name: kdc + containerPort: 8088 + - name: kdc-udp + containerPort: 8088 + protocol: UDP + resources: + requests: + memory: "384Mi" + cpu: "300m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + tcpSocket: + port: 8088 + readinessProbe: + tcpSocket: + port: 8088 + - name: kadmind + image: ${IMAGE} + imagePullPolicy: Always + command: + - /usr/sbin/kadmind + - -nofork + volumeMounts: + - name: config-vol + subPath: krb5.conf + mountPath: /etc/krb5.conf + - name: config-vol + subPath: kadm5.acl + mountPath: /etc/kadm5.acl + - name: data-vol + mountPath: /var/kerberos/krb5kdc + ports: + - name: admin + containerPort: 8749 + - name: kpasswd + containerPort: 8464 + - name: kpasswd-udp + containerPort: 8464 + protocol: UDP + resources: + requests: + memory: "384Mi" + cpu: "300m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + tcpSocket: + port: 8749 + readinessProbe: + tcpSocket: + port: 8749 + volumes: + - name: config-vol + configMap: + name: ${NAME}-${TEST_ID}-config + - name: data-vol + emptyDir: {} + triggers: + - type: ConfigChange +- apiVersion: v1 + kind: Service + metadata: + name: kerberos-${TEST_ID} + labels: + app: ${NAME} + service: kerberos + environment: test-${TEST_ID} + spec: + type: NodePort + ports: + - name: kdc + port: 8088 + targetPort: kdc + - name: kdc-udp + port: 8088 + protocol: UDP + targetPort: kdc-udp + - name: admin + port: 8749 + targetPort: admin + - name: kpasswd + port: 8464 + targetPort: kpasswd + - name: kpasswd-udp + port: 8464 + protocol: UDP + targetPort: kpasswd-udp + selector: + app: ${NAME} + service: kerberos + environment: test-${TEST_ID} diff --git a/src/com/redhat/c3i/util/Krb5Client.groovy b/src/com/redhat/c3i/util/Krb5Client.groovy new file mode 100644 index 0000000..9192ad8 --- /dev/null +++ b/src/com/redhat/c3i/util/Krb5Client.groovy @@ -0,0 +1,121 @@ +// Interact with a Kerberos 5 KDC. +// Mike Bonnet (mikeb@redhat.com), 2019-10-22 + +package com.redhat.c3i.util + +class Krb5Client implements Serializable { + String realm + String domain + String kdc_host + String admin_host + String kpasswd_host + String principal + String password + String keytab + String confDir + Boolean kinit + def steps + + def init() { + for (param in ['realm', 'domain', 'kdc_host', 'admin_host', 'kpasswd_host', 'principal']) { + if (!this."${param}") { + steps.error "The ${param} must be specified" + } + } + if (kinit == null) { + kinit = true + } + if (!confDir) { + confDir = "${steps.pwd(tmp: true)}/krb5/${principal.replace('/', '_')}" + } + steps.dir(confDir) { + if (!steps.fileExists('krb5.conf')) { + steps.writeFile file: 'krb5.conf', text: """\ + [libdefaults] + dns_lookup_kdc = false + dns_lookup_realm = false + dns_canonicalize_hostname = false + ticket_lifetime = 24h + renew_lifetime = 7d + forwardable = true + pkinit_anchors = FILE:/etc/pki/tls/certs/ca-bundle.crt + spake_preauth_groups = edwards25519 + default_realm = ${realm} + default_ccache_name = FILE:${confDir}/ccache + + [realms] + ${realm} = { + kdc = ${kdc_host} + admin_server = ${admin_host} + kpasswd_server = ${kpasswd_host} + } + + [domain_realm] + .${domain} = ${realm} + ${domain} = ${realm} + """.stripIndent() + if (kinit) { + if (keytab) { + steps.writeFile file: 'keytab', text: keytab, encoding: 'Base64' + run("kinit -V -k -t keytab -c ccache ${principal}") + } else if (password) { + run("kinit -V -c ccache ${principal} <<<'${password}'") + } else { + steps.error "Either a password or a keytab must be specified" + } + } + } + } + } + + def run(Closure body) { + init() + steps.withEnv(["KRB5_CONFIG=${confDir}/krb5.conf"]) { + return body() + } + } + + def run(Map args=[:], String cmd) { + return run({ steps.sh script: cmd, + returnStdout: args.returnStdout ?: false, + returnStatus: args.returnStatus ?: false }) + } + + def runAdmin(String cmd) { + if (!password) { + steps.error "The admin password must be specified" + } + run("${cmd} <<<'${password}'") + } + + def addPrincipal(String princ, String password) { + runAdmin("kadmin -p ${principal} add_principal -pw '${password}' ${princ}") + } + + def addService(String svc) { + runAdmin("kadmin -p ${principal} add_principal -randkey ${svc}") + } + + def getKeytab(String svc) { + steps.dir(steps.pwd(tmp: true)) { + def ktfile = "${svc.replace('/', '_')}.kt" + if (!steps.fileExists(ktfile)) { + runAdmin("kadmin -p ${principal} ktadd -k ${ktfile} ${svc}") + } + return steps.readFile(file: ktfile, encoding: 'Base64') + } + } + + def changePassword(String newpass) { + if (!password) { + steps.error "The current password must be specified" + } + run("""\ + kpasswd < + krb5.env.remove(key) + def client = krb5.client(kinit: false) + def exc = shouldFail { + client.init() + } + assertEquals("The ${key.replace('KRB5_', '').toLowerCase()} must be specified" as String, exc.message) + krb5.env.put(key, value) + } + } + + @Test + void testInitPasswd() { + def first = true + helper.registerAllowedMethod('fileExists', [String.class], { if (first) { first = false; false } else { true } }) + def client = krb5.client( + principal: 'testprinc', + password: 'testpass', + ) + client.init() + assertEquals(true, client.kinit) + assertTrue(helper.callStack.any { call -> + call.methodName == 'pwd' && + call.args[0].tmp == true + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'dir' && + call.args[0] == '/tmp/dir/krb5/testprinc' + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'writeFile' && + call.args[0].file == 'krb5.conf' + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script == "kinit -V -c ccache testprinc <<<'testpass'" + }) + } + + @Test + void testInitKeytab() { + def first = true + helper.registerAllowedMethod('fileExists', [String.class], { if (first) { first = false; false } else { true } }) + def client = krb5.client( + principal: 'testprinc', + keytab: 'a1b2c3', + ) + client.init() + assertEquals(true, client.kinit) + assertTrue(helper.callStack.any { call -> + call.methodName == 'pwd' && + call.args[0].tmp == true + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'dir' && + call.args[0] == '/tmp/dir/krb5/testprinc' + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'writeFile' && + call.args[0].file == 'krb5.conf' + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'writeFile' && + call.args[0].file == 'keytab' && + call.args[0].encoding == 'Base64' + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script == 'kinit -V -k -t keytab -c ccache testprinc' + }) + } + + @Test + void testRun() { + def first = true + helper.registerAllowedMethod('fileExists', [String.class], { if (first) { first = false; false } else { true } }) + def client = krb5.client( + principal: 'testprinc', + password: 'testpass' + ) + client.run('ls') + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script == "kinit -V -c ccache testprinc <<<'testpass'" + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'withEnv' && + call.args[0][0].startsWith('KRB5_CONFIG=') + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script == 'ls' && + call.args[0].returnStdout == false && + call.args[0].returnStatus == false + }) + } + + @Test + void testRunReturnStdout() { + def first = true + helper.registerAllowedMethod('fileExists', [String.class], { if (first) { first = false; false } else { true } }) + def client = krb5.client( + principal: 'testprinc', + password: 'testpass' + ) + client.run('ls', returnStdout: true) + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script == 'ls' && + call.args[0].returnStdout == true && + call.args[0].returnStatus == false + }) + } + + @Test + void testRunReturnStatus() { + def first = true + helper.registerAllowedMethod('fileExists', [String.class], { if (first) { first = false; false } else { true } }) + def client = krb5.client( + principal: 'testprinc', + password: 'testpass' + ) + client.run('ls', returnStatus: true) + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script == 'ls' && + call.args[0].returnStdout == false && + call.args[0].returnStatus == true + }) + } + + @Test + void testChangePassword() { + def first = true + helper.registerAllowedMethod('fileExists', [String.class], { if (first) { first = false; false } else { true } }) + def client = krb5.client( + principal: 'testprinc', + password: 'testpass' + ) + client.changePassword('newpass') + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script.startsWith('kpasswd < + call.methodName == 'sh' && + call.args[0].script == "ls <<<'testpass'" + }) + assertFalse(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script.startsWith('kinit') + }) + } + + @Test + void testAddPrincipal() { + def client = krb5.adminClient(password: 'testpass') + client.addPrincipal('newprinc', 'newpass') + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script == "kadmin -p kadmin/admin add_principal -pw 'newpass' newprinc <<<'testpass'" + }) + } + + @Test + void testAddService() { + def client = krb5.adminClient(password: 'testpass') + client.addService('new/svc') + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script == "kadmin -p kadmin/admin add_principal -randkey new/svc <<<'testpass'" + }) + } + + @Test + void testGetKeytab() { + helper.registerAllowedMethod('readFile', [Map.class], { 'ktdata' }) + def client = krb5.adminClient(password: 'testpass') + def result = client.getKeytab('some/svc') + assertEquals('ktdata', result) + assertEquals(2, helper.methodCallCount('pwd')) + assertEquals(2, helper.methodCallCount('dir')) + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script.startsWith('kadmin -p kadmin/admin ktadd') && + call.args[0].script.endsWith("some/svc <<<'testpass'") + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'readFile' && + call.args[0].file == 'some_svc.kt' && + call.args[0].encoding == 'Base64' + }) + } + + @Test + void testGetKeytabExists() { + helper.registerAllowedMethod('fileExists', [String.class], { true }) + helper.registerAllowedMethod('readFile', [Map.class], { 'ktdata' }) + def client = krb5.adminClient(password: 'testpass') + def result = client.getKeytab('some/svc') + assertEquals('ktdata', result) + assertEquals(1, helper.methodCallCount('pwd')) + assertEquals(1, helper.methodCallCount('dir')) + assertEquals(0, helper.methodCallCount('sh')) + assertTrue(helper.callStack.any { call -> + call.methodName == 'readFile' && + call.args[0].file == 'some_svc.kt' && + call.args[0].encoding == 'Base64' + }) + } + + @Test + void testWithKrbArgs() { + def first = true + helper.registerAllowedMethod('fileExists', [String.class], { if (first) { first = false; false } else { true } }) + def result = krb5.withKrb(principal: 'testprinc', password: 'testpass') { + return 'output' + } + assertEquals('output', result) + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script == "kinit -V -c ccache testprinc <<<'testpass'" + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'withEnv' && + call.args[0][0].startsWith('KRB5_CONFIG=') + }) + } + + @Test + void testWithKrbEnv() { + def first = true + helper.registerAllowedMethod('fileExists', [String.class], { if (first) { first = false; false } else { true } }) + krb5.env.KRB5_PRINCIPAL = 'testprinc' + krb5.env.KRB5_PASSWORD = 'testpass' + def result = krb5.withKrb() { + return 'output' + } + assertEquals('output', result) + assertTrue(helper.callStack.any { call -> + call.methodName == 'sh' && + call.args[0].script == "kinit -V -c ccache testprinc <<<'testpass'" + }) + assertTrue(helper.callStack.any { call -> + call.methodName == 'withEnv' && + call.args[0][0].startsWith('KRB5_CONFIG=') + }) + } + +} diff --git a/vars/krb5.groovy b/vars/krb5.groovy new file mode 100644 index 0000000..6e2188c --- /dev/null +++ b/vars/krb5.groovy @@ -0,0 +1,90 @@ +// Functions to deploy a containerized Kerberos KDC. +// Mike Bonnet (mikeb@redhat.com), 2019-10-22 + +/** + * Deploy a Kerberos 5 KDC suitable for testing. + * @param args.script The script calling the method. + * @param args.test_id A unique {@code String} used to identify this instance. + * @param args.realm The Kerberos realm to manage. + * @param args.domain The DNS domain to associate with the Kerberos realm. + * @param args.admin_password The password for the admin user. + * @param args.init_users A comma-separated list of initial users to define, in username:password format. + * @param args.image The pull spec of the Kerberos container image to use. + * @return An OpenShift selector representing the DeploymentConfigs rolled out. + */ +def deploy(Map args) { + if (!args.image) { + args.image = 'quay.io/factory2/krb5-fedora:latest' + } + def yaml = libraryResource "openshift/templates/krb5.yaml" + def template = readYaml text: yaml + def models = args.script.openshift.process(template, + '-p', "TEST_ID=${args.test_id}", + '-p', "REALM=${args.realm ?: 'CLUSTER.LOCAL'}", + '-p', "DOMAIN=${args.domain ?: 'cluster.local'}", + '-p', "ADMIN_PASSWORD=${args.admin_password}", + '-p', "INIT_USERS=${args.init_users ?: ''}", + '-p', "IMAGE=${args.image}", + '-l', 'c3i.redhat.com/app=krb5', + '-l', "c3i.redhat.com/test=${args.test_id}", + ) + return c3i.deploy(script: args.script, objs: models) +} + +/** + * Return a client that can be used for interacting with the KDC. + * @param args.principal The Kerberos principal to use to contact the KDC. If not specified, + * it will be retrieved from {@code env.KRB5_PRINCIPAL}. + * @param args.password The password for the Kerberos principal. If not specified, + * it will be retrieved from {@code env.KRB5_PASSWORD}. + * @param args.keytab The Base64-encoded keytab for the Kerberos principal. If not specified, + * it will be retrieved from {@code env.KRB5_KEYTAB}. + * @param args.realm The Kerberos realm. If not specified, + * it will be retrieved from the {@code env.KRB5_REALM} variable. + * @param args.domain The domain associated with the realm. If not specified, + * it will be retrieved from the {@code env.KRB5_DOMAIN} variable. + * @param args.kdc_host The hostname:port for the KDC. If not specified, + * it will be retrieved from the {@code env.KRB5_KDC_HOST} variable. + * @param args.admin_host The hostname:port for the admin server. If not specified, + * it will be retrieved from the {@code env.KRB5_ADMIN_HOST} variable. + * @param args.kpasswd_host The hostname:port for the kpasswd server. If not specified, + * it will be retrieved from the {@code env.KRB5_KPASSWD_HOST} variable. + * @return A {@code Krb5Client} instance. + */ +def client(Map args=[:]) { + args.principal = args.principal ?: env.KRB5_PRINCIPAL + args.password = args.password ?: env.KRB5_PASSWORD + args.keytab = args.keytab ?: env.KRB5_KEYTAB + args.realm = args.realm ?: env.KRB5_REALM + args.domain = args.domain ?: env.KRB5_DOMAIN + args.kdc_host = args.kdc_host ?: env.KRB5_KDC_HOST + args.admin_host = args.admin_host ?: env.KRB5_ADMIN_HOST + args.kpasswd_host = args.kpasswd_host ?: env.KRB5_KPASSWD_HOST + args.steps = steps + return new com.redhat.c3i.util.Krb5Client(args) +} + +/** + * Return a client that can be used for issuing admin commands to the KDC. + * @param args.password The admin password for the KDC. If not specified, + * it will be retrieved from {@code env.KRB5_ADMIN_PASSWORD} if + * defined, otherwise from {@code env.KRB5_PASSWORD}. + * @return A {@code Krb5Client} instance. + */ +def adminClient(Map args=[:]) { + args.principal = 'kadmin/admin' + args.password = args.password ?: env.KRB5_ADMIN_PASSWORD + args.kinit = false + return client(args) +} + +/** + * Run a block of code with Kerberos authentication configured. + * @params args The same arguments accepted by {@code client()}. + * @params body The {@code Closure} to execute. + * @return The return value of the {@code Closure}. + */ +def withKrb(Map args=[:], Closure body) { + def client = client(args) + return client.run(body) +} From cef7031f2726b265bb81e2923917281a821a8cd2 Mon Sep 17 00:00:00 2001 From: Mike Bonnet Date: Oct 28 2019 15:06:41 +0000 Subject: [PATCH 2/2] configure MBS for Kerberos authentication If the "frontend_keytab" and "krb5_conf_configmap" parameters are passed to "mbs.deploy()", the MBS frontend will be configured to require Kerberos authentication. Otherwise, it will require no authentication. --- diff --git a/resources/openshift/templates/mbs-frontend-krb5.yaml b/resources/openshift/templates/mbs-frontend-krb5.yaml new file mode 100644 index 0000000..bc0545c --- /dev/null +++ b/resources/openshift/templates/mbs-frontend-krb5.yaml @@ -0,0 +1,349 @@ +--- +apiVersion: v1 +kind: Template +metadata: + name: mbs-frontend-krb5-template + app: mbs +parameters: +- name: TEST_ID + displayName: Test id + description: Short unique identifier for this test run (e.g. Jenkins job number) + required: true +- name: MBS_FRONTEND_IMAGE + displayName: Image for MBS frontend + description: Image to be used for MBS frontend deployment + required: true +- name: KOJI_URL + displayName: Top level URL of the Koji instance to use + description: Top level URL of the Koji instance to use. Without a '/' at the end. + required: true +- name: FLASK_SECRET_KEY + displayName: The secret key for Flask + generate: expression + from: "[\\w]{16}" +- name: DATABASE_PASSWORD + displayName: Database password + description: The password for the database. + required: true +- name: FRONTEND_KEYTAB + displayName: Base64-encoded Kerberos keytab used by the frontend + required: true +- name: KRB5_CONF_CONFIGMAP + displayName: Name of the ConfigMap containing the krb5.conf required for Kerberos auth + required: true +- name: KRB5_USER + displayName: A user who will be allowed to authenticate to the MBS. + required: false + value: mbs-admin +objects: +- apiVersion: v1 + kind: Secret + metadata: + name: mbs-${TEST_ID}-frontend-keytab + labels: + app: mbs + service: frontend + environment: test-${TEST_ID} + data: + keytab: ${FRONTEND_KEYTAB} +- apiVersion: v1 + kind: ConfigMap + metadata: + name: mbs-${TEST_ID}-httpd-config + labels: + app: mbs + service: frontend + environment: test-${TEST_ID} + data: + mbs.conf: | + WSGIDaemonProcess mbs user=fedmsg group=fedmsg home=/usr/share/mbs maximum-requests=1000 display-name=mbs processes=2 threads=2 + WSGISocketPrefix run/wsgi + WSGIRestrictStdout Off + WSGIRestrictSignal Off + WSGIPythonOptimize 1 + WSGIApplicationGroup %{GLOBAL} + + WSGIScriptAlias / /usr/share/mbs/mbs.wsgi + + + WSGIProcessGroup mbs + + # Return JSON when authentication fails + ErrorDocument 401 "{\"error\": \"You must be authenticated to perform this action.\"}" + Header always set Content-Type "application/json" "expr=%{REQUEST_STATUS} == 401" + + AuthType GSSAPI + AuthName "MBS Kerberos negotiate authentication based on GSSAPI" + GssapiSSLonly On + GssapiCredStore keytab:/etc/mbs.keytab + + # GET methods are allowed for everyone, but any other method + # needs a valid-user. + + Require method GET OPTIONS + Require valid-user + + + + RedirectMatch ^/$ /module-build-service/1/module-builds/ +- apiVersion: v1 + kind: ConfigMap + metadata: + name: mbs-${TEST_ID}-frontend-config + labels: + app: mbs + service: frontend + environment: test-${TEST_ID} + data: + config.py: | + class ProdConfiguration(object): + DEBUG = True + + SECRET_KEY = '${FLASK_SECRET_KEY}' + + SQLALCHEMY_DATABASE_URI = 'postgresql://mbs:${DATABASE_PASSWORD}@mbs-${TEST_ID}-database:5432/mbs' + SQLALCHEMY_TRACK_MODIFICATIONS = True + + # Global network-related values, in seconds + NET_TIMEOUT = 120 + NET_RETRY_INTERVAL = 30 + + SYSTEM = 'koji' + MESSAGING = 'umb' + MESSAGING_TOPIC_PREFIX = ['/queue/Consumer.mbs.queue.VirtualTopic.eng'] + KOJI_CONFIG = '/etc/module-build-service/koji.conf' + KOJI_PROFILE = 'test' + ARCHES = ['x86_64'] + KOJI_PROXYUSER = False + KOJI_REPOSITORY_URL = '${KOJI_URL}/kojiroot/repos' + PDC_URL = '' + PDC_INSECURE = False + PDC_DEVELOP = True + SCMURLS = [] + ALLOW_CUSTOM_SCMURLS = True + + RESOLVER = 'koji' + + # This is a whitelist of prefixes of koji tags we're allowed to manipulate + KOJI_TAG_PREFIXES = ['module'] + + DEFAULT_DIST_TAG_PREFIX = 'module+' + + # Use the same priority as all other builds + KOJI_BUILD_PRIORITY = 0 + + # Control where modules get tagged post-build. + BASE_MODULE_NAMES = set(['platform']) + KOJI_CG_TAG_BUILD = False + KOJI_CG_BUILD_TAG_TEMPLATE = '' + KOJI_CG_DEFAULT_BUILD_TAG = '' + + # Enable authentication + NO_AUTH = False + + YAML_SUBMIT_ALLOWED = True + + # Allow maintainers to specify something that differs from the git branch. + ALLOW_NAME_OVERRIDE_FROM_SCM = False + ALLOW_STREAM_OVERRIDE_FROM_SCM = True + + # How often should we resort to polling, in seconds + # Set to zero to disable polling + POLLING_INTERVAL = 600 + + # Determines how many builds that can be submitted to the builder + # and be in the build state at a time. Set this to 0 for no restrictions + # New name + NUM_CONCURRENT_BUILDS = 5 + + RPMS_DEFAULT_REPOSITORY = 'git+https://src.fedoraproject.org/rpms/' + RPMS_ALLOW_REPOSITORY = True + RPMS_DEFAULT_CACHE = '' + RPMS_ALLOW_CACHE = False + MODULES_DEFAULT_REPOSITORY = '' + MODULES_ALLOW_REPOSITORY = False + MODULES_ALLOW_SCRATCH = True + + # Our per-build logs for the Koji content generator go here. + # CG imports are controlled by KOJI_ENABLE_CONTENT_GENERATOR + BUILD_LOGS_DIR = '/var/tmp' + + # Time after which MBS will delete koji targets it created. + KOJI_TARGET_DELETE_TIME = 86400 + + # Whether or not to import modules back to koji. + KOJI_ENABLE_CONTENT_GENERATOR = True + + # Available backends are: console, file. + LOG_BACKEND = 'console' + + # Available log levels are: debug, info, warn, error. + LOG_LEVEL = 'debug' + + REBUILD_STRATEGY_ALLOW_OVERRIDE = True + REBUILD_STRATEGY = 'only-changed' + + KOJI_EXTERNAL_REPO_URL_PREFIX = '${KOJI_URL}/kojiroot/' + + ALLOWED_PRIVILEGED_MODULE_NAMES = ['build'] + + # Settings for Kerberos + LDAP auth + AUTH_METHOD = 'kerberos' + KERBEROS_KEYTAB = '/etc/mbs.keytab' + LDAP_URI = '' + LDAP_GROUPS_DN = '' + # These groups are allowed to submit builds. + ALLOWED_GROUPS = [] + # These users don't need to be part of a group to submit builds. + ALLOWED_USERS = ['${KRB5_USER}'] + # These groups are allowed to cancel the builds of other users. + ADMIN_GROUPS = [] + # These groups are allowed to import the virtual module. + ALLOWED_GROUPS_TO_IMPORT_MODULE = ['packager'] + + KOJI_TAG_EXTRA_OPTS = {u'mock.package_manager': u'dnf', u'mock.yum.module_hotfixes': 1, u'repo_include_all': True, u'mock.new_chroot': 0} + + SCRATCH_BUILD_ONLY_BRANCHES = [ + r'^private-.*', + ] + koji.conf: | + [test] + server = ${KOJI_URL}/kojihub + weburl = ${KOJI_URL}/koji/ + topurl = ${KOJI_URL}/kojiroot/ + authtype = ssl + ;client certificate + cert = /etc/koji-certs/kojiadmin.crt + ;certificate of the CA that issued the client certificate + ;ca = /etc/koji-certs/clientca.crt + ;certificate of the CA that issued the HTTP server certificate + serverca = /etc/koji-certs/koji_ca_cert.crt + mock.cfg: | + config_opts['root'] = '$root' + config_opts['target_arch'] = '$arch' + config_opts['legal_host_arches'] = ('$arch',) + config_opts['chroot_setup_cmd'] = 'install $group' + config_opts['dist'] = '' + config_opts['extra_chroot_dirs'] = [ '/run/lock', ] + config_opts['releasever'] = '' + config_opts['package_manager'] = 'dnf' + config_opts['nosync'] = True + config_opts['use_bootstrap_container'] = False + + config_opts['yum.conf'] = """ + $yum_conf + """ + yum.conf: | + [main] + keepcache=1 + debuglevel=2 + reposdir=/dev/null + logfile=/var/log/yum.log + retries=20 + obsoletes=1 + gpgcheck=0 + assumeyes=1 + syslog_ident=mock + syslog_device= + install_weak_deps=0 + metadata_expire=3600 + mdpolicy=group:primary + + # repos +- apiVersion: v1 + kind: DeploymentConfig + metadata: + name: mbs-${TEST_ID}-frontend + labels: + app: mbs + service: frontend + environment: test-${TEST_ID} + spec: + replicas: 1 + selector: + app: mbs + service: frontend + environment: test-${TEST_ID} + strategy: + type: Rolling + template: + metadata: + labels: + app: mbs + service: frontend + environment: test-${TEST_ID} + spec: + containers: + - name: frontend + image: "${MBS_FRONTEND_IMAGE}" + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: http + - containerPort: 8443 + name: https + livenessProbe: + httpGet: + path: /module-build-service/1/monitor/metrics + port: http + readinessProbe: + httpGet: + path: /module-build-service/1/module-builds/?per_page=1&short=true + port: http + volumeMounts: + - name: fedmsg-config + mountPath: /etc/fedmsg.d + - name: frontend-certs + mountPath: /etc/mbs-certs + - name: mbs-config + mountPath: /etc/module-build-service + - name: httpd-config + subPath: mbs.conf + mountPath: /etc/httpd/conf.d/mbs.conf + - name: wsgi-config + mountPath: /usr/share/mbs + - name: koji-certs + mountPath: /etc/koji-certs + - name: cacerts-vol + subPath: cert-bundle + mountPath: /etc/pki/tls/cert.pem + - name: krb5-conf-vol + subPath: krb5.conf + mountPath: /etc/krb5.conf + - name: keytab-vol + subPath: keytab + mountPath: /etc/mbs.keytab + resources: + limits: + memory: 400Mi + cpu: 300m + volumes: + - name: fedmsg-config + configMap: + name: mbs-${TEST_ID}-frontend-fedmsg-config + - name: frontend-certs + secret: + secretName: mbs-${TEST_ID}-frontend-certs + - name: mbs-config + configMap: + name: mbs-${TEST_ID}-frontend-config + - name: httpd-config + configMap: + name: mbs-${TEST_ID}-httpd-config + - name: wsgi-config + configMap: + name: mbs-${TEST_ID}-wsgi-config + - name: koji-certs + secret: + secretName: mbs-${TEST_ID}-koji-secrets + - name: cacerts-vol + configMap: + name: mbs-${TEST_ID}-cacerts + - name: krb5-conf-vol + configMap: + name: ${KRB5_CONF_CONFIGMAP} + - name: keytab-vol + secret: + secretName: mbs-${TEST_ID}-frontend-keytab + triggers: + - type: ConfigChange diff --git a/resources/openshift/templates/mbs-frontend-noauth.yaml b/resources/openshift/templates/mbs-frontend-noauth.yaml new file mode 100644 index 0000000..aad7f26 --- /dev/null +++ b/resources/openshift/templates/mbs-frontend-noauth.yaml @@ -0,0 +1,305 @@ +--- +apiVersion: v1 +kind: Template +metadata: + name: mbs-frontend-noauth-template + app: mbs +parameters: +- name: TEST_ID + displayName: Test id + description: Short unique identifier for this test run (e.g. Jenkins job number) + required: true +- name: MBS_FRONTEND_IMAGE + displayName: Image for MBS frontend + description: Image to be used for MBS frontend deployment + required: true +- name: KOJI_URL + displayName: Top level URL of the Koji instance to use + description: Top level URL of the Koji instance to use. Without a '/' at the end. + required: true +- name: FLASK_SECRET_KEY + displayName: The secret key for Flask + generate: expression + from: "[\\w]{16}" +- name: DATABASE_PASSWORD + displayName: Database password + description: The password for the database. + required: true +objects: +- apiVersion: v1 + kind: ConfigMap + metadata: + name: mbs-${TEST_ID}-httpd-config + labels: + app: mbs + service: frontend + environment: test-${TEST_ID} + data: + mbs.conf: | + WSGIDaemonProcess mbs user=fedmsg group=fedmsg home=/usr/share/mbs maximum-requests=1000 display-name=mbs processes=2 threads=2 + WSGISocketPrefix run/wsgi + WSGIRestrictStdout Off + WSGIRestrictSignal Off + WSGIPythonOptimize 1 + WSGIApplicationGroup %{GLOBAL} + + WSGIScriptAlias / /usr/share/mbs/mbs.wsgi + + + WSGIProcessGroup mbs + + # Return JSON when authentication fails + ErrorDocument 401 "{\"error\": \"You must be authenticated to perform this action.\"}" + Header always set Content-Type "application/json" "expr=%{REQUEST_STATUS} == 401" + + # No authentication methods avalable. + Require all granted + + + RedirectMatch ^/$ /module-build-service/1/module-builds/ +- apiVersion: v1 + kind: ConfigMap + metadata: + name: mbs-${TEST_ID}-frontend-config + labels: + app: mbs + service: frontend + environment: test-${TEST_ID} + data: + config.py: | + class ProdConfiguration(object): + DEBUG = True + + SECRET_KEY = '${FLASK_SECRET_KEY}' + + SQLALCHEMY_DATABASE_URI = 'postgresql://mbs:${DATABASE_PASSWORD}@mbs-${TEST_ID}-database:5432/mbs' + SQLALCHEMY_TRACK_MODIFICATIONS = True + + # Global network-related values, in seconds + NET_TIMEOUT = 120 + NET_RETRY_INTERVAL = 30 + + SYSTEM = 'koji' + MESSAGING = 'umb' + MESSAGING_TOPIC_PREFIX = ['/queue/Consumer.mbs.queue.VirtualTopic.eng'] + KOJI_CONFIG = '/etc/module-build-service/koji.conf' + KOJI_PROFILE = 'test' + ARCHES = ['x86_64'] + KOJI_PROXYUSER = False + KOJI_REPOSITORY_URL = '${KOJI_URL}/kojiroot/repos' + PDC_URL = '' + PDC_INSECURE = False + PDC_DEVELOP = True + SCMURLS = [] + ALLOW_CUSTOM_SCMURLS = True + + RESOLVER = 'koji' + + # This is a whitelist of prefixes of koji tags we're allowed to manipulate + KOJI_TAG_PREFIXES = ['module'] + + DEFAULT_DIST_TAG_PREFIX = 'module+' + + # Use the same priority as all other builds + KOJI_BUILD_PRIORITY = 0 + + # Control where modules get tagged post-build. + BASE_MODULE_NAMES = set(['platform']) + KOJI_CG_TAG_BUILD = False + KOJI_CG_BUILD_TAG_TEMPLATE = '' + KOJI_CG_DEFAULT_BUILD_TAG = '' + + # Disable authentication + NO_AUTH = True + + YAML_SUBMIT_ALLOWED = True + + # Allow maintainers to specify something that differs from the git branch. + ALLOW_NAME_OVERRIDE_FROM_SCM = False + ALLOW_STREAM_OVERRIDE_FROM_SCM = True + + # How often should we resort to polling, in seconds + # Set to zero to disable polling + POLLING_INTERVAL = 600 + + # Determines how many builds that can be submitted to the builder + # and be in the build state at a time. Set this to 0 for no restrictions + # New name + NUM_CONCURRENT_BUILDS = 5 + + RPMS_DEFAULT_REPOSITORY = 'git+https://src.fedoraproject.org/rpms/' + RPMS_ALLOW_REPOSITORY = True + RPMS_DEFAULT_CACHE = '' + RPMS_ALLOW_CACHE = False + MODULES_DEFAULT_REPOSITORY = '' + MODULES_ALLOW_REPOSITORY = False + MODULES_ALLOW_SCRATCH = True + + # Our per-build logs for the Koji content generator go here. + # CG imports are controlled by KOJI_ENABLE_CONTENT_GENERATOR + BUILD_LOGS_DIR = '/var/tmp' + + # Time after which MBS will delete koji targets it created. + KOJI_TARGET_DELETE_TIME = 86400 + + # Whether or not to import modules back to koji. + KOJI_ENABLE_CONTENT_GENERATOR = True + + # Available backends are: console, file. + LOG_BACKEND = 'console' + + # Available log levels are: debug, info, warn, error. + LOG_LEVEL = 'debug' + + REBUILD_STRATEGY_ALLOW_OVERRIDE = True + REBUILD_STRATEGY = 'only-changed' + + KOJI_EXTERNAL_REPO_URL_PREFIX = '${KOJI_URL}/kojiroot/' + + ALLOWED_PRIVILEGED_MODULE_NAMES = ['build'] + + # Placeholder auth settings, unused + AUTH_METHOD = 'oidc' + # These groups are allowed to submit builds. + ALLOWED_GROUPS = [] + # These users don't need to be part of a group to submit builds. + ALLOWED_USERS = [] + # These groups are allowed to cancel the builds of other users. + ADMIN_GROUPS = [] + # These groups are allowed to import the virtual module. + ALLOWED_GROUPS_TO_IMPORT_MODULE = ['packager'] + + KOJI_TAG_EXTRA_OPTS = {u'mock.package_manager': u'dnf', u'mock.yum.module_hotfixes': 1, u'repo_include_all': True, u'mock.new_chroot': 0} + + SCRATCH_BUILD_ONLY_BRANCHES = [ + r'^private-.*', + ] + koji.conf: | + [test] + server = ${KOJI_URL}/kojihub + weburl = ${KOJI_URL}/koji/ + topurl = ${KOJI_URL}/kojiroot/ + authtype = ssl + ;client certificate + cert = /etc/koji-certs/kojiadmin.crt + ;certificate of the CA that issued the client certificate + ;ca = /etc/koji-certs/clientca.crt + ;certificate of the CA that issued the HTTP server certificate + serverca = /etc/koji-certs/koji_ca_cert.crt + mock.cfg: | + config_opts['root'] = '$root' + config_opts['target_arch'] = '$arch' + config_opts['legal_host_arches'] = ('$arch',) + config_opts['chroot_setup_cmd'] = 'install $group' + config_opts['dist'] = '' + config_opts['extra_chroot_dirs'] = [ '/run/lock', ] + config_opts['releasever'] = '' + config_opts['package_manager'] = 'dnf' + config_opts['nosync'] = True + config_opts['use_bootstrap_container'] = False + + config_opts['yum.conf'] = """ + $yum_conf + """ + yum.conf: | + [main] + keepcache=1 + debuglevel=2 + reposdir=/dev/null + logfile=/var/log/yum.log + retries=20 + obsoletes=1 + gpgcheck=0 + assumeyes=1 + syslog_ident=mock + syslog_device= + install_weak_deps=0 + metadata_expire=3600 + mdpolicy=group:primary + + # repos +- apiVersion: v1 + kind: DeploymentConfig + metadata: + name: mbs-${TEST_ID}-frontend + labels: + app: mbs + service: frontend + environment: test-${TEST_ID} + spec: + replicas: 1 + selector: + app: mbs + service: frontend + environment: test-${TEST_ID} + strategy: + type: Rolling + template: + metadata: + labels: + app: mbs + service: frontend + environment: test-${TEST_ID} + spec: + containers: + - name: frontend + image: "${MBS_FRONTEND_IMAGE}" + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: http + - containerPort: 8443 + name: https + livenessProbe: + httpGet: + path: /module-build-service/1/monitor/metrics + port: http + readinessProbe: + httpGet: + path: /module-build-service/1/module-builds/?per_page=1&short=true + port: http + volumeMounts: + - name: fedmsg-config + mountPath: /etc/fedmsg.d + - name: frontend-certs + mountPath: /etc/mbs-certs + - name: mbs-config + mountPath: /etc/module-build-service + - name: httpd-config + subPath: mbs.conf + mountPath: /etc/httpd/conf.d/mbs.conf + - name: wsgi-config + mountPath: /usr/share/mbs + - name: koji-certs + mountPath: /etc/koji-certs + - name: cacerts-vol + subPath: cert-bundle + mountPath: /etc/pki/tls/cert.pem + resources: + limits: + memory: 400Mi + cpu: 300m + volumes: + - name: fedmsg-config + configMap: + name: mbs-${TEST_ID}-frontend-fedmsg-config + - name: frontend-certs + secret: + secretName: mbs-${TEST_ID}-frontend-certs + - name: mbs-config + configMap: + name: mbs-${TEST_ID}-frontend-config + - name: httpd-config + configMap: + name: mbs-${TEST_ID}-httpd-config + - name: wsgi-config + configMap: + name: mbs-${TEST_ID}-wsgi-config + - name: koji-certs + secret: + secretName: mbs-${TEST_ID}-koji-secrets + - name: cacerts-vol + configMap: + name: mbs-${TEST_ID}-cacerts + triggers: + - type: ConfigChange diff --git a/resources/openshift/templates/mbs.yaml b/resources/openshift/templates/mbs.yaml index eac8ca3..9f6e0af 100644 --- a/resources/openshift/templates/mbs.yaml +++ b/resources/openshift/templates/mbs.yaml @@ -53,8 +53,8 @@ parameters: required: true - name: DATABASE_PASSWORD displayName: Database password - generate: expression - from: "[\\w]{32}" + description: The password for the database. + required: true - name: STOMP_URI displayName: Messagebus URI description: Messagebus URI @@ -155,150 +155,6 @@ objects: - apiVersion: v1 kind: ConfigMap metadata: - name: mbs-${TEST_ID}-frontend-config - labels: - app: mbs - service: frontend - environment: test-${TEST_ID} - data: - config.py: | - class ProdConfiguration(object): - DEBUG = True - - SECRET_KEY = '' - - SQLALCHEMY_DATABASE_URI = 'postgresql://mbs:${DATABASE_PASSWORD}@mbs-${TEST_ID}-database:5432/mbs' - SQLALCHEMY_TRACK_MODIFICATIONS = True - - # Global network-related values, in seconds - NET_TIMEOUT = 120 - NET_RETRY_INTERVAL = 30 - - SYSTEM = 'koji' - MESSAGING = 'umb' - MESSAGING_TOPIC_PREFIX = ['/queue/Consumer.mbs.queue.VirtualTopic.eng'] - KOJI_CONFIG = '/etc/module-build-service/koji.conf' - KOJI_PROFILE = 'test' - ARCHES = ['x86_64'] - KOJI_PROXYUSER = False - KOJI_REPOSITORY_URL = '' - PDC_URL = '' - PDC_INSECURE = True - PDC_DEVELOP = True - SCMURLS = [] - ALLOW_CUSTOM_SCMURLS = True - - RESOLVER = 'db' - - # This is a whitelist of prefixes of koji tags we're allowed to manipulate - KOJI_TAG_PREFIXES = ["module"] - - DEFAULT_DIST_TAG_PREFIX = 'module' - - # Use the same priority as all other builds - KOJI_BUILD_PRIORITY = 0 - - # Control where modules get tagged post-build. - BASE_MODULE_NAMES = ['platform'] - KOJI_CG_BUILD_TAG_TEMPLATE = '' - KOJI_CG_DEFAULT_BUILD_TAG = '' - - # Disable authentication - NO_AUTH = True - - YAML_SUBMIT_ALLOWED = True - - # Allow maintainers to specify something that differs from the git branch. - ALLOW_NAME_OVERRIDE_FROM_SCM = False - ALLOW_STREAM_OVERRIDE_FROM_SCM = False - - # How often should we resort to polling, in seconds - # Set to zero to disable polling - POLLING_INTERVAL = 600 - - # Determines how many builds that can be submitted to the builder - # and be in the build state at a time. Set this to 0 for no restrictions - NUM_CONCURRENT_BUILDS = 2 - - RPMS_DEFAULT_REPOSITORY = 'git+https://src.fedoraproject.org/rpms/' - RPMS_ALLOW_REPOSITORY = False - MODULES_DEFAULT_REPOSITORY = 'git+https://src.fedoraproject.org/modules/' - MODULES_ALLOW_REPOSITORY = False - - # Our per-build logs for the Koji content generator go here. - # CG imports are controlled by KOJI_ENABLE_CONTENT_GENERATOR - BUILD_LOGS_DIR = '/var/tmp' - - # Time after which MBS will delete koji targets it created. - KOJI_TARGET_DELETE_TIME = 86400 - - # Whether or not to import modules back to koji. - KOJI_ENABLE_CONTENT_GENERATOR = True - - # Available backends are: console, file. - LOG_BACKEND = 'console' - - # Available log levels are: debug, info, warn, error. - LOG_LEVEL = 'debug' - - REBUILD_STRATEGY_ALLOW_OVERRIDE = True - REBUILD_STRATEGY = 'only-changed' - - # Settings for Kerberos + LDAP auth - AUTH_METHOD = 'oidc' - # These groups are allowed to submit builds. - ALLOWED_GROUPS = [] - # These groups are allowed to import modules - ALLOWED_GROUPS_TO_IMPORT_MODULE = ['packager'] - # These groups are allowed to cancel the builds of other users. - ADMIN_GROUPS = [] - koji.conf: | - [test] - server = ${KOJI_URL}/kojihub - weburl = ${KOJI_URL}/koji/ - topurl = ${KOJI_URL}/kojiroot/ - authtype = ssl - ;client certificate - cert = /etc/koji-certs/kojiadmin.crt - ;certificate of the CA that issued the client certificate - ;ca = /etc/koji-certs/clientca.crt - ;certificate of the CA that issued the HTTP server certificate - serverca = /etc/koji-certs/koji_ca_cert.crt - mock.cfg: | - config_opts['root'] = '$root' - config_opts['target_arch'] = '$arch' - config_opts['legal_host_arches'] = ('$arch',) - config_opts['chroot_setup_cmd'] = 'install $group' - config_opts['dist'] = '' - config_opts['extra_chroot_dirs'] = [ '/run/lock', ] - config_opts['releasever'] = '' - config_opts['package_manager'] = 'dnf' - config_opts['nosync'] = True - config_opts['use_bootstrap_container'] = False - - config_opts['yum.conf'] = """ - $yum_conf - """ - yum.conf: | - [main] - keepcache=1 - debuglevel=2 - reposdir=/dev/null - logfile=/var/log/yum.log - retries=20 - obsoletes=1 - gpgcheck=0 - assumeyes=1 - syslog_ident=mock - syslog_device= - install_weak_deps=0 - metadata_expire=3600 - mdpolicy=group:primary - - # repos -- apiVersion: v1 - kind: ConfigMap - metadata: name: mbs-${TEST_ID}-httpd-config labels: app: mbs @@ -340,18 +196,15 @@ objects: from module_build_service import app as application - apiVersion: v1 - # Only creating this as a Secret because it supports base64-encoded data. - # Convert to a ConfigMap and use binaryData once we're running on OpenShift 3.10+. - kind: Secret + kind: ConfigMap metadata: name: mbs-${TEST_ID}-cacerts labels: app: mbs service: frontend environment: test-${TEST_ID} - data: - cert-bundle: |- - ${CA_CERTS} + binaryData: + cert-bundle: ${CA_CERTS} - apiVersion: v1 kind: Secret metadata: @@ -420,113 +273,6 @@ objects: tls: termination: passthrough insecureEdgeTerminationPolicy: Redirect -- apiVersion: v1 - kind: DeploymentConfig - metadata: - name: mbs-${TEST_ID}-frontend - labels: - app: mbs - service: frontend - environment: test-${TEST_ID} - spec: - replicas: 1 - selector: - app: mbs - service: frontend - environment: test-${TEST_ID} - strategy: - type: Rolling - template: - metadata: - labels: - app: mbs - service: frontend - environment: test-${TEST_ID} - spec: - containers: - - name: frontend - image: "${MBS_FRONTEND_IMAGE}" - imagePullPolicy: Always - ports: - - containerPort: 8080 - protocol: TCP - name: http - - containerPort: 8443 - protocol: TCP - name: https - livenessProbe: - failureThreshold: 3 - httpGet: - path: /module-build-service/1/monitor/metrics - port: 8080 - scheme: HTTP - initialDelaySeconds: 10 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 - readinessProbe: - failureThreshold: 3 - httpGet: - path: /module-build-service/1/module-builds/?per_page=1&short=true - port: 8080 - scheme: HTTP - initialDelaySeconds: 15 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 5 - volumeMounts: - - name: fedmsg-config - mountPath: /etc/fedmsg.d - readOnly: true - - name: frontend-certs - mountPath: /etc/mbs-certs - readOnly: true - - name: mbs-config - mountPath: /etc/module-build-service - readOnly: true - - name: httpd-config - mountPath: /etc/httpd/conf.d/mbs.conf - subPath: mbs.conf - readOnly: true - - name: wsgi-config - mountPath: /usr/share/mbs - readOnly: true - - name: koji-certs - mountPath: /etc/koji-certs - readOnly: true - - name: cacerts-vol - mountPath: /etc/pki/tls/cert.pem - subPath: cert-bundle - readOnly: true - resources: - limits: - memory: 400Mi - cpu: 300m - volumes: - - name: fedmsg-config - configMap: - name: mbs-${TEST_ID}-frontend-fedmsg-config - - name: frontend-certs - secret: - secretName: mbs-${TEST_ID}-frontend-certs - - name: mbs-config - configMap: - name: mbs-${TEST_ID}-frontend-config - - name: httpd-config - configMap: - name: mbs-${TEST_ID}-httpd-config - - name: wsgi-config - configMap: - name: mbs-${TEST_ID}-wsgi-config - - name: koji-certs - secret: - secretName: mbs-${TEST_ID}-koji-secrets - - name: cacerts-vol - secret: - secretName: mbs-${TEST_ID}-cacerts - defaultMode: 0444 - triggers: - - type: ConfigChange # backend - apiVersion: v1 kind: ConfigMap @@ -872,9 +618,8 @@ objects: secret: secretName: mbs-${TEST_ID}-koji-secrets - name: cacerts-vol - secret: - secretName: mbs-${TEST_ID}-cacerts - defaultMode: 0444 + configMap: + name: mbs-${TEST_ID}-cacerts triggers: - type: ConfigChange # postgresql diff --git a/vars/mbs.groovy b/vars/mbs.groovy index f2a2bc9..d9897c3 100644 --- a/vars/mbs.groovy +++ b/vars/mbs.groovy @@ -20,6 +20,14 @@ * @param args.frontendca A {@code Map} containing certificate data for the CA certificate that * issued the frontend certificate. The "cert" entry must contain the certificate in * text (PEM) format. + * @param args.frontend_keytab A Kerberos keytab to be used by the frontend for authentication, + * as a Base64-encoded {@String}. If not specified, Kerberos authentication will not be + * enabled. + * @param args.krb5_conf_configmap The name of the ConfigMap containing the krb5.conf required + * for Kerberos auth. If not specified, Kerberos authentication will not be enabled. + * @param args.krb5_user The name of a user who will be allowed to authenticate to the MBS + * via Kerberos. Kerberos support is not configured, this is ignored. If not specified, + * defaults to "mbs-admin". * @param args.cacerts A {@code Map} containing certificate data for the CA certificates that * should be trusted by MBS. The "cert" entry must contain the certificates in text (PEM) * format. @@ -37,6 +45,7 @@ def deploy(Map args) { if (!args.frontend_image) { args.frontend_image = 'quay.io/factory2/mbs-frontend:latest' } + def dbpasswd = UUID.randomUUID().toString().take(12) def yaml = libraryResource "openshift/templates/mbs.yaml" def template = readYaml text: yaml def models = args.script.openshift.process(template, @@ -51,10 +60,39 @@ def deploy(Map args) { '-p', "CA_CERTS=" + args.cacerts.bytes.encodeBase64().toString(), '-p', "KOJI_URL=${args.kojiurl}", '-p', "STOMP_URI=${args.stompuri}", + '-p', "DATABASE_PASSWORD=${dbpasswd}", '-p', "MBS_BACKEND_IMAGE=${args.backend_image}", '-p', "MBS_FRONTEND_IMAGE=${args.frontend_image}", '-l', 'c3i.redhat.com/app=mbs', '-l', "c3i.redhat.com/test=${args.test_id}", ) + def frontend + if (args.frontend_keytab && args.krb5_conf_configmap) { + yaml = libraryResource 'openshift/templates/mbs-frontend-krb5.yaml' + template = readYaml text: yaml + frontend = args.script.openshift.process(template, + '-p', "TEST_ID=${args.test_id}", + '-p', "KOJI_URL=${args.kojiurl}", + '-p', "FRONTEND_KEYTAB=${args.frontend_keytab}", + '-p', "KRB5_CONF_CONFIGMAP=${args.krb5_conf_configmap}", + '-p', "KRB5_USER=${args.krb5_user ?: 'mbs-admin'}", + '-p', "DATABASE_PASSWORD=${dbpasswd}", + '-p', "MBS_FRONTEND_IMAGE=${args.frontend_image}", + '-l', 'c3i.redhat.com/app=mbs', + '-l', "c3i.redhat.com/test=${args.test_id}", + ) + } else { + yaml = libraryResource 'openshift/templates/mbs-frontend-noauth.yaml' + template = readYaml text: yaml + frontend = args.script.openshift.process(template, + '-p', "TEST_ID=${args.test_id}", + '-p', "KOJI_URL=${args.kojiurl}", + '-p', "DATABASE_PASSWORD=${dbpasswd}", + '-p', "MBS_FRONTEND_IMAGE=${args.frontend_image}", + '-l', 'c3i.redhat.com/app=mbs', + '-l', "c3i.redhat.com/test=${args.test_id}", + ) + } + models.addAll(frontend) return c3i.deploy(script: args.script, objs: models) }