├── .ansible-lint
├── .github
└── workflows
│ └── tests.yml
├── .stickler.yml
├── KERBEROS.rst
├── MOLECULE.rst
├── README.rst
├── ansible.cfg
├── clone-koji-ansible
├── molecule
└── centos8
│ ├── Dockerfile.j2
│ ├── converge.yml
│ ├── molecule.yml
│ ├── prepare.yml
│ ├── roles
│ └── verify.yml
├── requirements.yml
├── roles
├── activemq
│ ├── files
│ │ ├── activemq.service
│ │ ├── activemq.sysconfig
│ │ └── activemq.xml
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ └── vars
│ │ └── main.yml
├── kdc
│ ├── files
│ │ ├── kdc.conf
│ │ ├── kdcproxy.conf
│ │ └── krb5.conf
│ ├── handlers
│ │ └── main.yml
│ ├── library
│ │ └── krb_principal.py
│ ├── tasks
│ │ ├── kdcproxy.yml
│ │ └── main.yml
│ ├── templates
│ │ └── kdcproxy-apache.conf.j2
│ └── vars
│ │ └── main.yml
├── koji-builder
│ ├── files
│ │ └── kojid.conf
│ ├── handlers
│ │ └── main.yml
│ └── tasks
│ │ └── main.yml
├── koji-client
│ ├── files
│ │ └── kojidev.conf
│ └── tasks
│ │ └── main.yml
├── koji-gc
│ ├── files
│ │ └── koji-gc.conf
│ ├── handlers
│ │ └── main.yml
│ └── tasks
│ │ └── main.yml
├── koji-hub
│ ├── files
│ │ ├── hub.conf
│ │ ├── kojihub.conf
│ │ └── ssl.conf
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ └── vars
│ │ ├── CentOS-7.yml
│ │ ├── CentOS-8.yml
│ │ ├── RedHat-7.yml
│ │ ├── RedHat-8.yml
│ │ └── main.yml
├── koji-ra
│ ├── files
│ │ └── kojira.conf
│ ├── handlers
│ │ └── main.yml
│ └── tasks
│ │ └── main.yml
├── koji-ssl-admin
│ ├── tasks
│ │ └── main.yml
│ └── vars
│ │ ├── CentOS-7.yml
│ │ ├── CentOS-8.yml
│ │ ├── RedHat-7.yml
│ │ └── RedHat-8.yml
├── koji-web
│ ├── files
│ │ ├── kojiweb.conf
│ │ └── web.conf
│ ├── handlers
│ │ └── main.yml
│ └── tasks
│ │ └── main.yml
├── postgresql
│ ├── handlers
│ │ └── main.yml
│ └── tasks
│ │ └── main.yml
└── rabbitmq
│ ├── files
│ └── rabbitmq.conf
│ ├── handlers
│ └── main.yml
│ └── tasks
│ └── main.yml
├── setup-koji.yml
└── vagrant
├── README.rst
├── Vagrantfile
├── reset-database.sh
├── roles
└── vagrant.yml
/.ansible-lint:
--------------------------------------------------------------------------------
1 | ---
2 | skip_list:
3 | - fqcn[action-core]
4 | - fqcn[action]
5 | - jinja
6 | - name[casing]
7 | - risky-file-permissions
8 | - role-name
9 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | syntax-check:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v3
10 | - name: Clone koji-ansible
11 | run: ./clone-koji-ansible
12 | - name: Install ansible
13 | run: |
14 | sudo apt-get update
15 | sudo apt-get purge ansible
16 | sudo apt-get install python3-setuptools
17 | pip3 install ansible --user
18 | - name: ansible-playbook syntax check
19 | run: |
20 | export PATH=$PATH:$HOME/.local/bin
21 | ansible-playbook -i localhost, setup-koji.yml --syntax-check
22 | ansible-lint:
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v3
26 | - name: Clone koji-ansible
27 | run: ./clone-koji-ansible
28 | - name: Install ansible-lint
29 | run: |
30 | sudo apt-get update
31 | sudo apt-get purge ansible
32 | sudo apt-get install python3-setuptools
33 | pip3 install ansible-lint[core] --user
34 | - name: Run ansible-lint
35 | run: |
36 | export PATH=$PATH:$HOME/.local/bin
37 | ansible-galaxy collection install -r requirements.yml
38 | ANSIBLE_LIBRARY=library ansible-lint -v roles/*
39 | molecule:
40 | runs-on: ubuntu-22.04
41 | steps:
42 | - uses: actions/checkout@v3
43 | - name: Clone koji-ansible
44 | run: ./clone-koji-ansible
45 | - name: Install podman and molecule
46 | run: |
47 | . /etc/os-release
48 | curl -sS https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/Release.key | gpg --dearmor | sudo dd of=/etc/apt/trusted.gpg.d/podman.gpg
49 | echo "deb [signed-by=/etc/apt/trusted.gpg.d/podman.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /" | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
50 | sudo apt-get update
51 | sudo apt-get purge ansible
52 | sudo apt-get install podman python3-setuptools
53 | sudo mkdir -p /etc/containers
54 | echo 'cgroup_manager="cgroupfs"' | sudo tee /etc/containers/libpod.conf
55 | pip3 install molecule molecule-plugins[podman] --user
56 | - name: Run molecule
57 | run: |
58 | export PATH=$PATH:$HOME/.local/bin
59 | export ANSIBLE_MODULE_UTILS=$(pwd)/module_utils
60 | export ANSIBLE_LIBRARY=$(pwd)/library
61 | molecule test -s centos8
62 |
--------------------------------------------------------------------------------
/.stickler.yml:
--------------------------------------------------------------------------------
1 | linters:
2 | ansible:
3 | ignore: 'ANSIBLE0106,ANSIBLE0501'
4 |
--------------------------------------------------------------------------------
/KERBEROS.rst:
--------------------------------------------------------------------------------
1 | Troubleshooting Kerberos
2 | ========================
3 |
4 | On the client::
5 |
6 | export KRB5_TRACE=/dev/stdout
7 |
8 | On the hub:
9 |
10 | Check ``/var/log/httpd/ssl_error_log``. You should see mod_auth_gssapi information here.
11 |
12 | On the KDC:
13 |
14 | Check ``/var/log/krb5kdc.log``. You should see an AS-REQ for the TGT and a TGS-REQ for HTTP/kojidev.example.com. Both should be under your user account (eg. "kdreyer").
15 |
16 | In molecule container (as root)::
17 |
18 | tail -F /var/log/httpd/ssl_error_log /var/log/httpd/ssl_access_log /var/log/krb5kdc.log
19 |
--------------------------------------------------------------------------------
/MOLECULE.rst:
--------------------------------------------------------------------------------
1 | molecule cheat sheet
2 | --------------------
3 |
4 | Simple one-shot create/run example::
5 |
6 | molecule test -s centos8
7 |
8 | To debug molecule, set the ``MOLECULE_NO_LOG=false`` environment variable and
9 | add the ``--debug`` command-line option. For example::
10 |
11 | MOLECULE_NO_LOG=false molecule --debug test -s centos8
12 |
13 | Instead of the all-in-one "test" sub-command, you can break down the steps
14 | into individual commands.
15 |
16 | - Create and start a container::
17 |
18 | molecule create -s centos8
19 |
20 | - Run the "converge" step, to apply the roles specified in
21 | ``converge.yml``::
22 |
23 | molecule converge -s centos8
24 |
25 | - Destroy the running container::
26 |
27 | molecule destroy -s centos8
28 |
29 | Verify that the container is running::
30 |
31 | podman ps -a
32 |
33 | (and look for a container named "instance".)
34 |
35 | Shell into the container instance::
36 |
37 | molecule login -s centos8
38 |
39 | (If that does not work, try ``podman exec -it instance /bin/sh``)
40 |
41 | Review the container's systemd log output::
42 |
43 | podman logs instance
44 |
45 | Clean up the local container image that molecule created::
46 |
47 | podman rmi localhost/molecule_local/centos:stream8
48 |
49 | Hints for parameterizing this and running it in CI:
50 |
51 | https://www.jeffgeerling.com/blog/2018/testing-your-ansible-roles-molecule
52 |
53 | https://www.jeffgeerling.com/blog/2020/travis-cis-new-pricing-plan-threw-wrench-my-open-source-works
54 |
55 | Replicating GitHub Actions locally
56 | -----------------------------
57 |
58 | GitHub Actions runs the tests in an Ubuntu Jammy VM. It can be tedious to push
59 | changes to GitHub, wait, and review the output. You may want to set up your
60 | own local Ubuntu Jammy VM when making large changes that impact the tests.
61 |
62 | Follow these instructions to set up Podman on your own Ubuntu Jammy VM in a
63 | similar way to GitHub Actions::
64 |
65 | sudo apt-get update
66 | # follow the steps in .github/workflows/...
67 |
68 | sudo apt-get -y install python3-pip
69 | pip3 install molecule[ansible,podman] --user
70 |
71 |
72 | Versioning
73 | ----------
74 |
75 | There are many bugs in podman, Ansible, and Molecule. Please run the latest
76 | versions of each.
77 |
78 | Version combinations that are known to work:
79 | - python3-molecule-3.0.5-2.fc32
80 | - ansible-2.9.14-1.fc32
81 | - podman-2.1.1-7.fc32
82 |
83 | Note that since we run systemd in the container, that means that the systemd
84 | version in CentOS 8 and the kernel version on the host (eg Fedora) sometimes
85 | interact poorly. See https://bugzilla.redhat.com/1853736 for an example.
86 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://github.com/ktdreyer/koji-playbooks/workflows/tests/badge.svg
2 | :target: https://github.com/ktdreyer/koji-playbooks/actions
3 |
4 | Ansible playbook(s) for automating the `Koji server install process
5 | `_.
6 |
7 | This sets up the Kerberos (GSSAPI) method of authentication. I use this to
8 | quickly set up Koji in VMs in OpenStack.
9 |
10 | Playbooks
11 | ---------
12 |
13 | * ``setup-koji.yml`` - Installs and configures a Kerberos KDC, koji-hub, and
14 | koji-builder.
15 |
16 | Run this playbook on a RHEL or CentOS 7 or 8 host with EPEL enabled. /mnt/koji
17 | should be a disk with plenty of space.
18 |
19 | SSL configuration
20 | -----------------
21 |
22 | * This playbook generates an SSL CA and keypair using the `koji-ssl-admin tool
23 | `_ .
24 |
25 | The Certificate Authority keypair:
26 | * ``/etc/pki/koji/koji-ca.crt``
27 | * ``/etc/pki/koji/koji-ca.key``
28 |
29 | The Apache web server HTTPS keypair (signed by koji-ca above):
30 | * ``/etc/pki/koji/kojidev.example.com.chain.crt``
31 | * ``/etc/pki/koji/kojidev.example.com.key``
32 |
33 | For GSSAPI (Kerberos) authentication, these are the only SSL certs you will
34 | need.
35 |
36 | The koji-hub role publishes the Koji CA at the following URL:
37 | https://kojidev.example.com/kojifiles/koji-ca.crt . External Koji clients
38 | can download this file to verify the HTTPS connections.
39 |
40 | Hard-coded things
41 | -----------------
42 |
43 | This is a santized code drop from a set of internal playbooks, so several
44 | things are currently hard-coded:
45 |
46 | * The hostname is hardcoded in several places as "kojidev.example.com".
47 |
48 | * The main username is hardcoded in several places as "kdreyer".
49 |
50 |
51 | Roles
52 | -----
53 |
54 | * ``roles/kdc`` - installs and configures a Kerberos KDC, and bootstraps all
55 | the keytabs we need.
56 |
57 | This will create a "kdreyer" Kerberos account. The ``koji-hub`` role will
58 | bootstrap this account into Koji's database. If you need more Kerberos
59 | users, add them here.
60 |
61 | * ``roles/koji-ssl-admin`` - Creates the SSL CA and HTTPS keypair for the Koji
62 | server.
63 |
64 | * ``roles/koji-client`` - Configures a ``kojidev`` script and `profile
65 | `_.
66 |
67 | * ``roles/postgresql`` - installs and configures PostgreSQL for Koji Hub
68 |
69 | * ``roles/koji-hub`` - installs and configures Koji Hub
70 |
71 | This role requires the `koji_host
72 | `_
73 | module from the `koji-ansible project
74 | `_.
75 |
76 | This role will bootstrap "kdreyer" as the first Koji administrator in the
77 | database.
78 |
79 | If you need more users, add them with the `koji_user
80 | `_
81 | module.
82 |
83 | * ``roles/koji-web`` - installs and configures the web interface for Koji.
84 |
85 | * ``roles/koji-builder`` - installs and configures a Koji builder.
86 |
87 | * ``roles/koji-ra`` - installs and configures the Koji "ra" (repository admin)
88 | service.
89 |
90 | * ``roles/koji-gc`` - installs and configures the Koji garbage collector
91 | service.
92 |
93 | * ``roles/activemq`` - installs and configures an ActiveMQ 5 broker for testing
94 | the Koji Hub protonmsg plugin.
95 |
96 | * ``roles/rabbitmq`` - installs and configures a RabbitMQ broker for testing
97 | the Koji Hub protonmsg plugin.
98 |
99 | See Also
100 | --------
101 |
102 | For managing resources within your Koji hub, please see the
103 | https://github.com/ktdreyer/koji-ansible project.
104 |
--------------------------------------------------------------------------------
/ansible.cfg:
--------------------------------------------------------------------------------
1 | [defaults]
2 | retry_files_enabled = False
3 | stdout_callback = yaml
4 |
5 | [ssh_connection]
6 | pipelining=True
7 |
--------------------------------------------------------------------------------
/clone-koji-ansible:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -eux
4 |
5 | git clone --depth 1 https://github.com/ktdreyer/koji-ansible
6 | pushd koji-ansible && git log HEAD -1 --no-decorate && popd
7 | ln -s koji-ansible/library/ library
8 | ln -s ../koji-ansible/library/ vagrant/library
9 | ln -s koji-ansible/module_utils/ module_utils
10 | ln -s ../koji-ansible/module_utils/ vagrant/module_utils
11 |
--------------------------------------------------------------------------------
/molecule/centos8/Dockerfile.j2:
--------------------------------------------------------------------------------
1 | {% if item.registry is defined %}
2 | FROM {{ item.registry.url }}/{{ item.image }}
3 | {% else %}
4 | FROM {{ item.image }}
5 | {% endif %}
6 |
7 | # Ensure package prerequisites
8 | # (This is simplified from the Molecule project upstream, plus
9 | # glibc-langpack-en for postgres)
10 |
11 | RUN dnf makecache \
12 | && dnf --assumeyes install \
13 | /usr/bin/python3 \
14 | /usr/bin/python3-config \
15 | /usr/bin/dnf-3 \
16 | sudo \
17 | bash \
18 | iproute \
19 | glibc-langpack-en
20 |
21 | # Create "ansible" unprivileged user with sudo permissions:
22 |
23 | ENV ANSIBLE_USER=ansible SUDO_GROUP=wheel
24 | RUN set -xe \
25 | && groupadd -r ${ANSIBLE_USER} \
26 | && useradd -m -g ${ANSIBLE_USER} ${ANSIBLE_USER} \
27 | && usermod -aG ${SUDO_GROUP} ${ANSIBLE_USER} \
28 | && sed -i "/^%${SUDO_GROUP}/s/ALL\$/NOPASSWD:ALL/g" /etc/sudoers
29 |
--------------------------------------------------------------------------------
/molecule/centos8/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | # Note, "become: no" does not work in Ansible 2.9's podman connection plugin.
5 | # See https://github.com/ansible/ansible/pull/70541
6 | become: yes
7 | become_method: sudo
8 | tasks:
9 | - name: Add EPEL yum repository
10 | yum_repository:
11 | name: epel
12 | description: epel
13 | metalink: https://mirrors.fedoraproject.org/metalink?repo=epel-8&arch=$basearch
14 | gpgcheck: true
15 | gpgkey: https://archive.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8
16 | roles:
17 | - kdc
18 | - koji-ssl-admin
19 | - koji-client
20 | - postgresql
21 | - koji-hub
22 | - koji-web
23 | - koji-builder
24 | - koji-ra
25 | - koji-gc
26 |
--------------------------------------------------------------------------------
/molecule/centos8/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | options:
5 | requirements-file: requirements.yml
6 | driver:
7 | name: podman
8 | platforms:
9 | - name: instance
10 | registry:
11 | url: quay.io/centos
12 | image: centos:stream8
13 | privileged: true
14 | command: "/usr/sbin/init"
15 | tty: True
16 | etc_hosts:
17 | kojidev.example.com: 127.0.0.1
18 | hostname: kojidev.example.com
19 | provisioner:
20 | name: ansible
21 | inventory:
22 | host_vars:
23 | instance:
24 | ansible_user: ansible
25 | # Silence warnings in Ansible 2.9:
26 | ansible_python_interpreter: /usr/bin/python3
27 | verifier:
28 | name: ansible
29 |
--------------------------------------------------------------------------------
/molecule/centos8/prepare.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Prepare
3 | hosts: all
4 | become: yes
5 | become_method: sudo
6 | tasks:
7 | - name: Add EPEL yum repository
8 | yum_repository:
9 | name: epel
10 | description: epel
11 | metalink: https://mirrors.fedoraproject.org/metalink?repo=epel-8&arch=$basearch
12 | gpgcheck: true
13 | gpgkey: https://archive.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8
14 |
--------------------------------------------------------------------------------
/molecule/centos8/roles:
--------------------------------------------------------------------------------
1 | ../../roles/
--------------------------------------------------------------------------------
/molecule/centos8/verify.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # This is an example playbook to execute Ansible tests.
3 |
4 | - name: Verify
5 | hosts: all
6 | tasks:
7 | - name: Example assertion
8 | assert:
9 | that: true
10 |
--------------------------------------------------------------------------------
/requirements.yml:
--------------------------------------------------------------------------------
1 | ---
2 | collections:
3 | - ansible.posix
4 | - community.crypto
5 | - community.general
6 | - community.postgresql
7 | - community.rabbitmq
8 | # Note: CI tests (GitHub Actions) use a Git clone of ktdreyer.koji_ansible
9 | # intead of requiring it here.
10 |
--------------------------------------------------------------------------------
/roles/activemq/files/activemq.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Apache ActiveMQ
3 | After=network-online.target
4 |
5 | [Service]
6 | Type=forking
7 | WorkingDirectory=/opt/activemq/bin
8 | EnvironmentFile=/etc/sysconfig/activemq
9 | ExecStart=/opt/activemq/bin/activemq start
10 | ExecStop=/opt/activemq/bin/activemq stop
11 | Restart=on-abort
12 | User=activemq
13 | Group=activemq
14 | PIDFile=/opt/activemq/data/activemq.pid
15 | ProtectSystem=strict
16 | ProtectHome=yes
17 | ReadWritePaths=/opt
18 |
19 | [Install]
20 | WantedBy=multi-user.target
21 |
--------------------------------------------------------------------------------
/roles/activemq/files/activemq.sysconfig:
--------------------------------------------------------------------------------
1 | ACTIVEMQ_SSL_OPTS="-Djavax.net.debug=ssl,keymanager -Djavax.net.ssl.keyStoreType=pkcs12 -Djavax.net.ssl.keyStore=/opt/activemq/kojidev.example.com.pkcs12 -Djavax.net.ssl.keyStorePassword=kojipass -Djavax.net.ssl.trustStore=/opt/activemq/koji-ca.ks -Djavax.net.ssl.trustStorePassword=kojipass"
2 |
--------------------------------------------------------------------------------
/roles/activemq/files/activemq.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
23 |
24 |
25 |
26 |
27 | file:${activemq.conf}/credentials.properties
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
75 |
76 |
77 |
78 |
79 |
86 |
87 |
88 |
89 |
90 |
91 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/roles/activemq/handlers/main.yml:
--------------------------------------------------------------------------------
1 | # Pick up our custom sytemd unit file:
2 | - name: reload systemd
3 | systemd:
4 | daemon_reload: true
5 |
6 | - name: restart activemq
7 | service:
8 | name: activemq
9 | state: restarted
10 |
--------------------------------------------------------------------------------
/roles/activemq/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: install prerequisites
2 | package:
3 | name:
4 | - java-1.8.0-openjdk-headless
5 | - python3-pyOpenSSL
6 | state: present
7 |
8 | - name: create activemq user
9 | user:
10 | name: activemq
11 | shell: /sbin/nologin
12 |
13 | - name: download activemq tarball
14 | get_url:
15 | url: "https://archive.apache.org/dist/activemq/{{ activemq_version }}/apache-activemq-{{ activemq_version }}-bin.tar.gz"
16 | dest: "/tmp/apache-activemq-{{ activemq_version }}-bin.tar.gz"
17 | checksum: "{{ activemq_checksum }}"
18 | register: activemq_download
19 |
20 | - name: unpack activemq tarball # noqa no-handler
21 | unarchive:
22 | src: "/tmp/apache-activemq-{{ activemq_version }}-bin.tar.gz"
23 | dest: /opt
24 | remote_src: true
25 | owner: activemq
26 | group: activemq
27 | when: activemq_download.changed
28 |
29 | - name: symlink /opt/activemq
30 | file:
31 | src: "/opt/apache-activemq-{{ activemq_version }}"
32 | dest: /opt/activemq
33 | state: link
34 |
35 | - name: copy /opt/activemq/conf/activemq.xml
36 | copy:
37 | src: files/activemq.xml
38 | dest: /opt/activemq/conf/activemq.xml
39 | owner: root
40 | group: root
41 | mode: "0644"
42 | notify:
43 | - restart activemq
44 |
45 | - name: install activemq systemd config file
46 | copy:
47 | src: files/activemq.sysconfig
48 | dest: /etc/sysconfig/activemq
49 | owner: root
50 | group: root
51 | mode: "0644"
52 | notify:
53 | - restart activemq
54 |
55 | - name: install activemq systemd unit file
56 | copy:
57 | src: files/activemq.service
58 | dest: /etc/systemd/system/activemq.service
59 | owner: root
60 | group: root
61 | mode: "0644"
62 | notify:
63 | - reload systemd
64 |
65 | - name: combine activemq server cert into a pkcs12 file
66 | openssl_pkcs12:
67 | path: /opt/activemq/kojidev.example.com.pkcs12
68 | friendly_name: koji
69 | privatekey_path: /etc/pki/koji/kojidev.example.com.key
70 | certificate_path: /etc/pki/koji/kojidev.example.com.crt
71 | other_certificates: /etc/pki/koji/koji-ca.crt
72 | passphrase: kojipass
73 | owner: activemq
74 | group: activemq
75 | mode: "0400"
76 | state: present
77 |
78 | # TODO: use https://docs.ansible.com/ansible/2.9/modules/java_cert_module.html
79 | # Verify with the -list command:
80 | # keytool -list -v -keystore /opt/activemq/koji-ca.ks
81 | - name: transform koji-ca.crt file to java keystore file
82 | command:
83 | argv:
84 | - keytool
85 | - -import
86 | - -trustcacerts
87 | - -alias
88 | - kojica
89 | - -file
90 | - /etc/pki/koji/koji-ca.crt
91 | - -keystore
92 | - /opt/activemq/koji-ca.ks
93 | - -storepass
94 | - kojipass
95 | - -noprompt
96 | creates: /opt/activemq/koji-ca.ks
97 |
98 |
99 | - name: start activemq
100 | service:
101 | name: activemq
102 | enabled: true
103 | state: started
104 |
--------------------------------------------------------------------------------
/roles/activemq/vars/main.yml:
--------------------------------------------------------------------------------
1 | activemq_version: "5.16.3"
2 | activemq_checksum: sha256:1846da2985ec64253ecc41a54f1477731eb4750fe840a9dd9fdfee88e5c94252
3 |
--------------------------------------------------------------------------------
/roles/kdc/files/kdc.conf:
--------------------------------------------------------------------------------
1 | [kdcdefaults]
2 | kdc_ports = 88
3 | kdc_tcp_ports = 88
4 |
5 | [realms]
6 | KOJIDEV.EXAMPLE.COM = {
7 | #master_key_type = aes256-cts
8 | acl_file = /var/kerberos/krb5kdc/kadm5.acl
9 | dict_file = /usr/share/dict/words
10 | admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab
11 | supported_enctypes = aes256-cts:normal camellia256-cts:normal
12 | }
13 |
--------------------------------------------------------------------------------
/roles/kdc/files/kdcproxy.conf:
--------------------------------------------------------------------------------
1 | [global]
2 | use_dns = false
3 | configs = mit
4 |
--------------------------------------------------------------------------------
/roles/kdc/files/krb5.conf:
--------------------------------------------------------------------------------
1 | # Configuration snippets may be placed in this directory as well
2 | includedir /etc/krb5.conf.d/
3 |
4 | [logging]
5 | default = FILE:/var/log/krb5libs.log
6 | kdc = FILE:/var/log/krb5kdc.log
7 | admin_server = FILE:/var/log/kadmind.log
8 |
9 | [libdefaults]
10 | dns_lookup_realm = false
11 | ticket_lifetime = 24h
12 | renew_lifetime = 7d
13 | forwardable = true
14 | rdns = false
15 | pkinit_anchors = /etc/pki/tls/certs/ca-bundle.crt
16 | default_realm = KOJIDEV.EXAMPLE.COM
17 |
18 | [realms]
19 | KOJIDEV.EXAMPLE.COM = {
20 | kdc = kojidev.example.com
21 | admin_server = kojidev.example.com
22 | }
23 |
24 | [domain_realm]
25 | # .example.com = EXAMPLE.COM
26 | # example.com = EXAMPLE.COM
27 |
--------------------------------------------------------------------------------
/roles/kdc/handlers/main.yml:
--------------------------------------------------------------------------------
1 | - name: restart krb5kdc
2 | service:
3 | name: krb5kdc
4 | state: restarted
5 |
6 | - name: restart httpd
7 | service:
8 | name: httpd
9 | state: restarted
10 |
--------------------------------------------------------------------------------
/roles/kdc/library/krb_principal.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import errno
3 | import re
4 | import os
5 | import subprocess
6 | from ansible.module_utils.basic import AnsibleModule
7 | from ansible.module_utils.six import PY2
8 |
9 |
10 | ANSIBLE_METADATA = {
11 | 'metadata_version': '1.0',
12 | 'status': ['preview'],
13 | 'supported_by': 'community'
14 | }
15 |
16 |
17 | DOCUMENTATION = '''
18 | ---
19 | module: krb_principal
20 |
21 | short_description: Create Kerberos principals and export keytabs
22 | description:
23 | - This module uses /usr/sbin/kadmin.local to create users and export
24 | keytab files.
25 |
26 | options:
27 | name:
28 | description:
29 | - The name of the kerberos principal, for example "kdreyer@EXAMPLE.COM"
30 | required: true
31 | state:
32 | description:
33 | - The only allowed value is "present".
34 | required: false
35 | choices: [present]
36 | keytab:
37 | description:
38 | - If specified, Ansible will extract a .keytab file to this path.
39 | - If any file already exists at this path, then Ansible will delete the
40 | file and create a new keytab if Ansible also created a new principal.
41 | If Ansible did not create a new principal, then it will not delete or
42 | edit this file if it exists.
43 | - Ansible does not validate that the pre-existing file is a keytab.
44 | required: false
45 | requirements:
46 | - "python >= 2.7"
47 | '''
48 |
49 | EXAMPLES = '''
50 | - name: Create a user and a keytab.
51 | hosts: localhost
52 | tasks:
53 | - name: Create a kdreyer keytab
54 | krb_principal:
55 | name: kdreyer@EXAMPLE.COM
56 | keytab: /var/local/kdreyer.keytab
57 | '''
58 |
59 |
60 | def kadmin(query):
61 | """ Call "kadmin.local -q" with this query. """
62 | cmd = ('/usr/sbin/kadmin.local', '-q', query)
63 | output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
64 | if PY2:
65 | return output
66 | return output.decode('utf-8')
67 |
68 |
69 | def get_principal(name):
70 | """ Return the full principal name, or None if it does not exist. """
71 | output = kadmin('getprinc %s' % name)
72 | for line in output.splitlines():
73 | if line.startswith('Principal:'):
74 | _, principal = line.split(':')
75 | return principal
76 |
77 |
78 | def create_principal(name):
79 | """ Create an account, and return the full principal name. """
80 | output = kadmin('addprinc -randkey %s' % name)
81 | for line in output.splitlines():
82 | m = re.match('Principal "[^"]+" created.', line)
83 | if m:
84 | return m.group(0)
85 | raise RuntimeError('could not scrape addprinc output: %s' % output)
86 |
87 |
88 | def extract_keytab(principal, keytab):
89 | """ Extract a keytab file for a principal. """
90 | kadmin('ktadd -k %s -norandkey %s' % (keytab, principal))
91 |
92 |
93 | def run_module():
94 | module_args = dict(
95 | name=dict(required=True),
96 | keytab=dict(type='path'),
97 | state=dict(choices=['present'], default='present'),
98 | )
99 | module = AnsibleModule(
100 | argument_spec=module_args,
101 | supports_check_mode=True
102 | )
103 |
104 | check_mode = module.check_mode
105 | params = module.params
106 | name = params['name']
107 | keytab = params['keytab']
108 |
109 | changes = []
110 | principal = get_principal(name)
111 | if not principal:
112 | changes.append('create principal %s' % name)
113 | if not check_mode:
114 | principal = create_principal(name)
115 |
116 | if keytab:
117 | if changes and not check_mode:
118 | # Delete the keytab
119 | try:
120 | os.remove(keytab)
121 | except OSError as e:
122 | if e.errno != errno.ENOENT:
123 | raise
124 | if not os.path.exists(keytab):
125 | changes.append('extract keytab %s' % keytab)
126 | if not check_mode:
127 | extract_keytab(principal, keytab)
128 |
129 | if not changes:
130 | module.exit_json(changed=False)
131 |
132 | module.exit_json(changed=True, stdout_lines=changes)
133 |
134 |
135 | def main():
136 | run_module()
137 |
138 |
139 | if __name__ == '__main__':
140 | main()
141 |
--------------------------------------------------------------------------------
/roles/kdc/tasks/kdcproxy.yml:
--------------------------------------------------------------------------------
1 | - name: set python values for el7
2 | set_fact:
3 | # No "python3" package name prefix.
4 | python_kdcproxy: python-kdcproxy
5 | # See /usr/lib/rpm/macros.d/macros.python2 for %python2_sitelib
6 | python_sitelib: /usr/lib/python2.7/site-packages
7 | mod_wsgi: mod_wsgi
8 | when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int == 7
9 |
10 | - name: Add yum repository to work around rhbz1827758 for el8
11 | yum_repository:
12 | name: bz1827758
13 | description: work around rhbz1827758
14 | baseurl: https://fedorapeople.org/~ktdreyer/bz1827758/
15 | gpgcheck: false
16 | when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int == 8
17 |
18 | - name: Install kdcproxy packages
19 | package:
20 | name:
21 | - httpd
22 | - mod_ssl
23 | - "{{ mod_wsgi }}"
24 | - "{{ python_kdcproxy }}"
25 | state: present
26 | retries: 100
27 | delay: 3
28 |
29 | - name: copy /etc/httpd/conf.d/kdcproxy.conf
30 | template:
31 | src: kdcproxy-apache.conf.j2
32 | dest: /etc/httpd/conf.d/kdcproxy.conf
33 | owner: root
34 | group: root
35 | mode: "0644"
36 | notify:
37 | - restart httpd
38 |
39 | - name: copy /etc/kdcproxy.conf
40 | copy:
41 | src: files/kdcproxy.conf
42 | dest: /etc/kdcproxy.conf
43 | owner: root
44 | group: root
45 | mode: "0644"
46 | notify:
47 | - restart httpd
48 |
--------------------------------------------------------------------------------
/roles/kdc/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: configure FQDN in /etc/hosts
2 | copy:
3 | dest: '/etc/hosts'
4 | content: |
5 | 127.0.0.1 localhost
6 | ::1 localhost
7 | 127.0.0.1 kojidev.example.com
8 | mode: "0644"
9 | when:
10 | - ansible_virtualization_type | default('') not in ['docker', 'podman', 'container', 'containerd']
11 | # Docker, Podman, and containerd will mount /etc/hosts as RO tmpfs.
12 | - ansible_mounts | selectattr('mount', 'equalto', '/etc/hosts') | list | length == 0
13 |
14 | - name: Install Kerberos packages
15 | package:
16 | name:
17 | - krb5-server
18 | - krb5-workstation
19 | state: present
20 |
21 | - name: copy /etc/krb5.conf
22 | copy:
23 | src: files/krb5.conf
24 | dest: /etc/krb5.conf
25 | owner: root
26 | group: root
27 | mode: "0644"
28 | notify:
29 | - restart krb5kdc
30 |
31 | - name: copy /var/kerberos/krb5kdc/kdc.conf
32 | copy:
33 | src: files/kdc.conf
34 | dest: /var/kerberos/krb5kdc/kdc.conf
35 | owner: root
36 | group: root
37 | mode: "0600"
38 | notify:
39 | - restart krb5kdc
40 |
41 | - name: configure /var/kerberos/krb5kdc/kadm5.acl
42 | copy:
43 | dest: /var/kerberos/krb5kdc/kadm5.acl
44 | owner: root
45 | group: root
46 | mode: "0600"
47 | content: |
48 | */admin@KOJIDEV.EXAMPLE.COM *
49 | notify:
50 | - restart krb5kdc
51 |
52 | - name: initialize KDC DB
53 | command: /usr/sbin/kdb5_util create -s -P test
54 | args:
55 | creates: /var/kerberos/krb5kdc/principal.ok
56 | notify:
57 | - restart krb5kdc
58 |
59 | - name: start krb5kdc
60 | service:
61 | name: krb5kdc
62 | enabled: true
63 | state: started
64 |
65 | - name: start kadmin
66 | service:
67 | name: kadmin
68 | enabled: true
69 | state: started
70 |
71 | - name: Create Kerberos principals
72 | krb_principal:
73 | name: "{{ item }}"
74 | keytab: "/var/local/{{ item | replace ('/', '.') }}.keytab"
75 | with_items:
76 | - kdreyer
77 | - rcm/debbuild
78 | - HTTP/kojidev.example.com
79 | - compile/kojidev.example.com
80 | - koji/kojiweb
81 | - koji/kojira
82 | - koji/garbagecollector
83 | tags:
84 | - krb_account
85 |
86 | - name: set up kdcproxy
87 | include_tasks: kdcproxy.yml
88 |
--------------------------------------------------------------------------------
/roles/kdc/templates/kdcproxy-apache.conf.j2:
--------------------------------------------------------------------------------
1 | WSGIDaemonProcess kdcproxy processes=2 threads=15 maximum-requests=1000 \
2 | display-name=%{GROUP}
3 | WSGIImportScript {{ python_sitelib }}/kdcproxy/__init__.py \
4 | process-group=kdcproxy application-group=kdcproxy
5 | WSGIScriptAlias /KdcProxy {{ python_sitelib }}/kdcproxy/__init__.py
6 | WSGIScriptReloading Off
7 |
8 |
9 | Satisfy Any
10 | Order Deny,Allow
11 | Allow from all
12 | WSGIProcessGroup kdcproxy
13 | WSGIApplicationGroup kdcproxy
14 | SSLRequireSSL
15 |
16 |
--------------------------------------------------------------------------------
/roles/kdc/vars/main.yml:
--------------------------------------------------------------------------------
1 | python_kdcproxy: python3-kdcproxy
2 | python_sitelib: /usr/lib/python3.6/site-packages
3 | mod_wsgi: python3-mod_wsgi
4 |
--------------------------------------------------------------------------------
/roles/koji-builder/files/kojid.conf:
--------------------------------------------------------------------------------
1 | [kojid]
2 | ; The number of seconds to sleep between tasks
3 | ; sleeptime=15
4 |
5 | ; The maximum number of jobs that kojid will handle at a time
6 | ; maxjobs=10
7 |
8 | ; The minimum amount of free space (in MBs) required for each build root
9 | ; minspace=8192
10 |
11 | ; The directory root where work data can be found from the koji hub
12 | ; topdir=/mnt/koji
13 |
14 | ; The directory root for temporary storage
15 | ; workdir=/tmp/koji
16 |
17 | ; The temporary directory in buildroot
18 | ; chroot_tmpdir = /chroot_tmpdir
19 |
20 | ; The directory root for mock
21 | ; mockdir=/var/lib/mock
22 |
23 | ; The user to run as when doing builds
24 | ; mockuser=kojibuilder
25 |
26 | ; The vendor to use in rpm headers
27 | ; vendor=Koji
28 |
29 | ; The packager to use in rpm headers
30 | ; packager=Koji
31 |
32 | ; The distribution to use in rpm headers
33 | ; distribution=Koji
34 |
35 | ; The _host string to use in mock
36 | ; mockhost=koji-linux-gnu
37 |
38 | ; Timeout for build duration (24 hours)
39 | ; rpmbuild_timeout=86400
40 |
41 | ; Install timeout(seconds) for image build
42 | ; Default value is 0, which means using the number in /etc/oz/oz.cfg,
43 | ; supported since oz-0.16.0
44 | ; oz_install_timeout=7200
45 |
46 | ; The URL for the xmlrpc server
47 | server=https://kojidev.example.com/kojihub
48 |
49 | ; The URL for the file access
50 | ; Cannot use https with our custom CA here yet, see
51 | ; https://github.com/rpm-software-management/mock/issues/588
52 | topurl=http://kojidev.example.com/kojifiles
53 |
54 | ; use createrepo_c rather than createrepo
55 | ; use_createrepo_c=False
56 |
57 | ; A space-separated list of tuples from which kojid is allowed to checkout.
58 | ; The format of those tuples is:
59 | ;
60 | ; host:repository[:use_common[:source_cmd]]
61 | ;
62 | ; Incorrectly-formatted tuples will be ignored.
63 | ;
64 | ; If use_common is not present, kojid will attempt to checkout a common/
65 | ; directory from the repository. If use_common is set to no, off, false, or 0,
66 | ; it will not attempt to checkout a common/ directory.
67 | ;
68 | ; source_cmd is a shell command (args separated with commas instead of spaces)
69 | ; to run before building the srpm. It is generally used to retrieve source
70 | ; files from a remote location. If no source_cmd is specified, "make sources"
71 | ; is run by default.
72 | allowed_scms=
73 | !src.fedoraproject.org:/pagure/fork/*
74 | !src.fedoraproject.org:/pagure/forks/*
75 | src.fedoraproject.org:/*:false:fedpkg,sources
76 |
77 | ; The mail host to use for sending email notifications
78 | ;smtphost=example.com
79 |
80 | ; The From address used when sending email notifications
81 | ;from_addr=Koji Build System
82 |
83 | ;configuration for Kerberos authentication
84 |
85 | ;the format of the principal used by the build hosts
86 | ;%s will be replaced by the FQDN of the host
87 | ;host_principal_format = compile/%s@KOJIDEV.EXAMPLE.COM
88 | ; kojid uses socket.fqdn() to find the FQDN for a host. That code looks at the
89 | ; public IP address for a host and does a DNS lookup for the PTR record of that
90 | ; IP address. If the host is running in a cloud environment, that is almost
91 | ; certainly not what you want, and the PTR record will be something out of your
92 | ; control.
93 | ; You can override the (imho broken) auto-discovery by setting krb_principal
94 | ; directly here:
95 | krb_principal = compile/kojidev.example.com@KOJIDEV.EXAMPLE.COM
96 |
97 | ;location of the keytab
98 | ;keytab = /etc/kojid/kojid.keytab
99 | keytab = /var/local/compile.kojidev.example.com.keytab
100 |
101 | ;the service name of the principal being used by the hub
102 | ;krbservice = host
103 |
104 | ;configuration for SSL authentication
105 |
106 | ;client certificate
107 | ;cert = /etc/kojid/client.crt
108 |
109 | ;certificate of the CA that issued the HTTP server certificate
110 | ;serverca = /etc/kojid/serverca.crt
111 |
112 | ;if set to True, failing subtask will not automatically cancel other siblings
113 | ;build_arch_can_fail = False
114 |
115 | ;if set to True additional logs with timestamps will get created and uploaded
116 | ;to hub. It could be useful for debugging purposes, but creates twice as many
117 | ;log files
118 | ;log_timestamps = False
119 |
--------------------------------------------------------------------------------
/roles/koji-builder/handlers/main.yml:
--------------------------------------------------------------------------------
1 | - name: restart kojid
2 | service:
3 | name: kojid
4 | state: restarted
5 |
--------------------------------------------------------------------------------
/roles/koji-builder/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Add yum repository to work around rhbz1661580
2 | yum_repository:
3 | name: bz1661580
4 | description: work around rhbz1661580
5 | baseurl: https://fedorapeople.org/~ktdreyer/bz1661580/
6 | gpgcheck: false
7 | when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int == 7
8 |
9 | - name: Install the very latest requests-kerberos (rhbz1661580)
10 | package:
11 | name:
12 | - python-requests-kerberos
13 | - python2-kerberos
14 | state: latest # noqa package-latest
15 | when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int == 7
16 | retries: 100
17 | delay: 3
18 |
19 | - name: Install Koji builder packages
20 | package:
21 | name: koji-builder
22 | state: present
23 |
24 | - name: copy /etc/kojid/kojid.conf
25 | copy:
26 | src: files/kojid.conf
27 | dest: /etc/kojid/kojid.conf
28 | owner: root
29 | group: root
30 | mode: "0644"
31 | notify:
32 | - restart kojid
33 |
34 | # Because we have "topurl=https://..." in kojid.conf, we must trust this CA
35 | # system-wide because we query this URL with urllib2.
36 |
37 | - name: link our Koji CA to the system-wide location
38 | file:
39 | src: /etc/pki/koji/koji-ca.crt
40 | dest: /etc/pki/ca-trust/source/anchors/koji-ca.crt
41 | state: link
42 | register: koji_ca_anchor
43 |
44 | - name: trust our new Koji CA system-wide # noqa no-handler
45 | command: update-ca-trust extract
46 | when: koji_ca_anchor.changed
47 | changed_when: true
48 | notify:
49 | - restart kojid
50 |
51 | - name: start kojid
52 | service:
53 | name: kojid
54 | enabled: true
55 | state: started
56 |
--------------------------------------------------------------------------------
/roles/koji-client/files/kojidev.conf:
--------------------------------------------------------------------------------
1 | [kojidev]
2 | server = https://kojidev.example.com/kojihub
3 | authtype = kerberos
4 | topdir = /mnt/koji
5 | weburl = https://kojidev.example.com/koji
6 | topurl = https://kojidev.example.com/kojifiles
7 |
8 | # soon to be optional? see https://pagure.io/koji/pull-request/1194
9 | serverca = /etc/pki/koji/koji-ca.crt
10 |
--------------------------------------------------------------------------------
/roles/koji-client/tasks/main.yml:
--------------------------------------------------------------------------------
1 | # Set up the client for authentication
2 |
3 | - name: Install Koji client packages
4 | package:
5 | name:
6 | - koji
7 | - krb5-workstation
8 | state: present
9 |
10 | - name: copy koji client profile configuration
11 | copy:
12 | src: files/kojidev.conf
13 | dest: /etc/koji.conf.d/kojidev.conf
14 | owner: root
15 | group: root
16 | mode: "0644"
17 |
18 | - name: symlink "kojidev" alias for this profile
19 | file:
20 | src: /usr/bin/koji
21 | dest: /usr/bin/kojidev
22 | state: link
23 |
24 | - name: determine unprivileged user
25 | set_fact:
26 | unprivileged_user: "{{ ansible_user }}"
27 | become: false
28 |
29 | - name: determine unprivileged passwd info
30 | getent:
31 | database: passwd
32 | key: "{{ unprivileged_user }}"
33 |
34 | - name: determine unprivileged uid number
35 | set_fact:
36 | unprivileged_uid: "{{ getent_passwd[unprivileged_user][1] }}"
37 |
38 | - name: "create directory /var/kerberos/krb5/user/{{ unprivileged_uid }}"
39 | file:
40 | path: "/var/kerberos/krb5/user/{{ unprivileged_uid }}"
41 | state: directory
42 | mode: "0700"
43 | owner: "{{ unprivileged_user }}"
44 |
45 | - name: symlink kdreyer.keytab for unprivileged user
46 | file:
47 | src: /var/local/kdreyer.keytab
48 | dest: "/var/kerberos/krb5/user/{{ unprivileged_uid }}/client.keytab"
49 | state: link
50 |
51 | - name: make keytab readable by unprivileged user
52 | file:
53 | path: /var/local/kdreyer.keytab
54 | owner: "{{ unprivileged_user }}"
55 |
--------------------------------------------------------------------------------
/roles/koji-gc/files/koji-gc.conf:
--------------------------------------------------------------------------------
1 | #test policy file
2 | #earlier = higher precedence!
3 |
4 | [main]
5 | key_aliases =
6 | 30C9ECF8 fedora-test
7 | 4F2A6FD2 fedora-gold
8 | 897DA07A redhat-beta
9 | 1AC70CE6 fedora-extras
10 |
11 | unprotected_keys =
12 | fedora-test
13 | fedora-extras
14 | redhat-beta
15 |
16 | server = https://kojidev.example.com/kojihub
17 | weburl = https://kojidev.example.com/koji
18 |
19 | keytab = /var/local/koji.garbagecollector.keytab
20 |
21 | # The service name of the principal being used by the hub
22 | #krbservice = host
23 |
24 | ## The realm of server principal. Using client's realm if not set
25 | # krb_server_realm = EXAMPLE.COM
26 |
27 | # The domain name that will be appended to Koji usernames
28 | # when creating email notifications
29 | #email_domain = fedoraproject.org
30 |
31 | # SMTP user and pass (uncomment and fill in if your smtp server requires authentication)
32 | #smtp_user=user@example.com
33 | #smtp_pass=CHANGEME
34 |
35 | [prune]
36 | policy =
37 | #stuff to protect
38 | #note that tags with master lock engaged are already protected
39 | tag *-updates :: keep
40 | age < 1 day :: skip
41 | sig fedora-gold :: skip
42 | sig fedora-test && age < 12 weeks :: keep
43 |
44 | #stuff to chuck semi-rapidly
45 | tag *-testing *-candidate :: { # nested rules
46 | order >= 2 :: untag
47 | order > 0 && age > 6 weeks :: untag
48 | } #closing braces must be on a line by themselves (modulo comments/whitespace)
49 | tag *-candidate && age > 60 weeks :: untag
50 |
51 | #default: keep the last 3
52 | order > 2 :: untag
53 |
--------------------------------------------------------------------------------
/roles/koji-gc/handlers/main.yml:
--------------------------------------------------------------------------------
1 | # Pick up our custom sytemd unit files:
2 | - name: reload systemd
3 | systemd:
4 | daemon_reload: true
5 |
--------------------------------------------------------------------------------
/roles/koji-gc/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Install Koji utils package
2 | package:
3 | name: koji-utils
4 | state: present
5 |
6 | - name: copy /etc/koji-gc/koji-gc.conf
7 | copy:
8 | src: files/koji-gc.conf
9 | dest: /etc/koji-gc/koji-gc.conf
10 | owner: root
11 | group: root
12 | mode: "0644"
13 |
14 | - name: enable koji-gc.timer
15 | service:
16 | name: koji-gc.timer
17 | enabled: true
18 |
--------------------------------------------------------------------------------
/roles/koji-hub/files/hub.conf:
--------------------------------------------------------------------------------
1 | [hub]
2 |
3 | ## ConfigParser style config file, similar to ini files
4 | ## https://docs.python.org/library/configparser.html
5 | ##
6 | ## Note that multiline values can be set by indenting subsequent lines
7 | ## (which means you should not indent regular lines)
8 |
9 | ## Basic options ##
10 | DBName = koji
11 | DBUser = koji
12 | KojiDir = /mnt/koji
13 |
14 | ## Auth-related options ##
15 | # Use user IP in session management
16 | # CheckClientIP = True
17 |
18 | ## Kerberos authentication options ##
19 |
20 | AuthPrincipal = HTTP/kojidev.example.com@KOJIDEV.EXAMPLE.COM
21 | AuthKeytab = /var/local/HTTP.kojidev.example.com.keytab
22 | ProxyPrincipals = koji/kojiweb@KOJIDEV.EXAMPLE.COM
23 | ## format string for host principals (%s = hostname)
24 | HostPrincipalFormat = compile/%s@KOJIDEV.EXAMPLE.COM
25 |
26 | ## end Kerberos auth configuration
27 |
28 |
29 |
30 | ## SSL client certificate auth configuration ##
31 | #note: ssl auth may also require editing the httpd config (conf.d/kojihub.conf)
32 |
33 | ## the client username is the common name of the subject of their client certificate
34 | # DNUsernameComponent = CN
35 | ## separate multiple DNs with |
36 | # ProxyDNs = /C=US/ST=Massachusetts/O=Example Org/OU=Example User/CN=example/emailAddress=example@example.com
37 |
38 | ## end SSL client certificate auth configuration
39 |
40 |
41 |
42 | ## Other options ##
43 | LoginCreatesUser = On
44 | KojiWebURL = https://kojidev.example.com/koji
45 | # The domain name that will be appended to Koji usernames
46 | # when creating email notifications
47 | #EmailDomain = example.com
48 | # whether to send the task owner and package owner email or not on success. this still goes to watchers
49 | NotifyOnSuccess = False
50 | ## Disables all notifications
51 | DisableNotifications = True
52 |
53 | ## Resource limits ##
54 | ## All standard RLIMIT_* can be set here
55 | # RLIMIT_AS =
56 | # RLIMIT_CORE =
57 | # RLIMIT_CPU =
58 | # RLIMIT_DATA =
59 | # RLIMIT_FSIZE =
60 | # RLIMIT_MEMLOCK =
61 | # RLIMIT_NOFILE =
62 | # RLIMIT_NPROC =
63 | # RLIMIT_OFILE =
64 | # RLIMIT_RSS =
65 | # RLIMIT_STACK =
66 |
67 | ## If memory consumption raises during handling request for more
68 | ## than MemoryWarnThreshold kilobytes, warning is emitted to log
69 | # MemoryWarnThreshold = 5000
70 |
71 | ## Maximum request length can be limited on python-side
72 | # MaxRequestLength = 4194304
73 |
74 |
75 | ## Extended features
76 | ## Support Maven builds
77 | # EnableMaven = False
78 | ## Support Windows builds
79 | # EnableWin = False
80 |
81 | ## Koji hub plugins
82 | ## The path where plugins are found
83 | # PluginPath = /usr/lib/koji-hub-plugins
84 | ## A space-separated list of plugins to load
85 | # Plugins = echo
86 |
87 | ## If KojiDebug is on, the hub will be /very/ verbose and will report exception
88 | ## details to clients for anticipated errors (i.e. koji's own exceptions --
89 | ## subclasses of koji.GenericError).
90 | # KojiDebug = On
91 | #
92 | ## Log level/format for python logging module at hub
93 | # LogLevel = WARNING
94 | # LogFormat = %(asctime)s [%(levelname)s] m=%(method)s u=%(user_name)s p=%(process)s r=%(remoteaddr)s %(name)s: %(message)s'
95 | #
96 | #
97 | ## If VerbosePolicy (or KojiDebug) is on, 'policy violation'
98 | ## messages will contain also policy rule which caused this denial
99 | ## VerbosePolicy = False
100 | #
101 | ## If MissingPolicyOk is on, and given policy is not set up,
102 | ## policy test will pass as ok. If 'deny' result is desired, set it
103 | ## to off
104 | # MissingPolicyOk = True
105 | #
106 | ## Determines how much detail about exceptions is reported to the client (via faults)
107 | ## Meaningful values:
108 | ## normal - a basic traceback (format_exception)
109 | ## extended - an extended traceback (format_exc_plus)
110 | ## anything else - no traceback, just the error message
111 | ## The extended traceback is intended for debugging only and should NOT be
112 | ## used in production, since it may contain sensitive information.
113 | # KojiTraceback = normal
114 |
115 | ## These options are intended for planned outages
116 | # ServerOffline = False
117 | # OfflineMessage = temporary outage
118 | # LockOut = False
119 | ## If ServerOffline is True, the server will always report a ServerOffline fault (with
120 | ## OfflineMessage as the fault string).
121 | ## If LockOut is True, the server will report a ServerOffline fault for all non-admin
122 | ## requests.
123 |
--------------------------------------------------------------------------------
/roles/koji-hub/files/kojihub.conf:
--------------------------------------------------------------------------------
1 | #
2 | # koji-hub is an xmlrpc interface to the Koji database
3 | #
4 |
5 | Alias /kojihub /usr/share/koji-hub/kojiapp.py
6 | # Local Git clone:
7 | #Alias /kojihub /usr/local/koji/kojihub/app/kojiapp.py
8 |
9 |
10 | Options ExecCGI
11 | SetHandler wsgi-script
12 | WSGIApplicationGroup %{GLOBAL}
13 | # ^ works around a hub issue with OpenSSL
14 | # see: https://cryptography.io/en/latest/faq/#starting-cryptography-using-mod-wsgi-produces-an-internalerror-during-a-call-in-register-osrandom-engine
15 | WSGIScriptReloading Off
16 | # ^ reloading breaks hub "firstcall" check
17 | # see: https://pagure.io/koji/issue/875
18 |
19 | Order allow,deny
20 | Allow from all
21 |
22 | = 2.4>
23 | Require all granted
24 |
25 |
26 |
27 | # Local Git clone:
28 | #
29 | # Options ExecCGI
30 | # SetHandler wsgi-script
31 | # WSGIApplicationGroup %{GLOBAL}
32 | # Require all granted
33 | #
34 |
35 | # Also serve /mnt/koji
36 | Alias /kojifiles "/mnt/koji/"
37 |
38 |
39 | Options Indexes SymLinksIfOwnerMatch
40 | #If your top /mnt/koji directory is not owned by the httpd user, then
41 | #you will need to follow all symlinks instead, e.g.
42 | #Options Indexes FollowSymLinks
43 | AllowOverride None
44 | IndexOptions +NameWidth=*
45 |
46 | Order allow,deny
47 | Allow from all
48 |
49 | = 2.4>
50 | Require all granted
51 |
52 |
53 |
54 | # uncomment this to enable authentication via SSL client certificates
55 | #
56 | # SSLVerifyClient require
57 | # SSLVerifyDepth 10
58 | # SSLOptions +StdEnvVars
59 | #
60 |
61 | # GSSAPI authentication:
62 |
63 | AuthType GSSAPI
64 | AuthName "GSSAPI Single Sign On Login"
65 | GssapiCredStore keytab:/var/local/HTTP.kojidev.example.com.keytab
66 | Require valid-user
67 |
68 |
69 |
--------------------------------------------------------------------------------
/roles/koji-hub/files/ssl.conf:
--------------------------------------------------------------------------------
1 | #
2 | # When we also provide SSL we have to listen to the
3 | # the HTTPS port in addition.
4 | #
5 | Listen 443 https
6 |
7 | ##
8 | ## SSL Global Context
9 | ##
10 | ## All SSL configuration in this context applies both to
11 | ## the main server and all SSL-enabled virtual hosts.
12 | ##
13 |
14 | # Pass Phrase Dialog:
15 | # Configure the pass phrase gathering process.
16 | # The filtering dialog program (`builtin' is a internal
17 | # terminal dialog) has to provide the pass phrase on stdout.
18 | SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog
19 |
20 | # Inter-Process Session Cache:
21 | # Configure the SSL Session Cache: First the mechanism
22 | # to use and second the expiring timeout (in seconds).
23 | SSLSessionCache shmcb:/run/httpd/sslcache(512000)
24 | SSLSessionCacheTimeout 300
25 |
26 | # Pseudo Random Number Generator (PRNG):
27 | # Configure one or more sources to seed the PRNG of the
28 | # SSL library. The seed data should be of good random quality.
29 | # WARNING! On some platforms /dev/random blocks if not enough entropy
30 | # is available. This means you then cannot use the /dev/random device
31 | # because it would lead to very long connection times (as long as
32 | # it requires to make more entropy available). But usually those
33 | # platforms additionally provide a /dev/urandom device which doesn't
34 | # block. So, if available, use this one instead. Read the mod_ssl User
35 | # Manual for more details.
36 | SSLRandomSeed startup file:/dev/urandom 256
37 | SSLRandomSeed connect builtin
38 | #SSLRandomSeed startup file:/dev/random 512
39 | #SSLRandomSeed connect file:/dev/random 512
40 | #SSLRandomSeed connect file:/dev/urandom 512
41 |
42 | #
43 | # Use "SSLCryptoDevice" to enable any supported hardware
44 | # accelerators. Use "openssl engine -v" to list supported
45 | # engine names. NOTE: If you enable an accelerator and the
46 | # server does not start, consult the error logs and ensure
47 | # your accelerator is functioning properly.
48 | #
49 | SSLCryptoDevice builtin
50 | #SSLCryptoDevice ubsec
51 |
52 | ##
53 | ## SSL Virtual Host Context
54 | ##
55 |
56 |
57 |
58 | # General setup for the virtual host, inherited from global configuration
59 | #DocumentRoot "/var/www/html"
60 | #ServerName www.example.com:443
61 |
62 | # Use separate log files for the SSL virtual host; note that LogLevel
63 | # is not inherited from httpd.conf.
64 | ErrorLog logs/ssl_error_log
65 | TransferLog logs/ssl_access_log
66 | LogLevel warn
67 |
68 | # SSL Engine Switch:
69 | # Enable/Disable SSL for this virtual host.
70 | SSLEngine on
71 |
72 | # SSL Protocol support:
73 | # List the enable protocol levels with which clients will be able to
74 | # connect. Disable SSLv2 access by default:
75 | SSLProtocol all -SSLv2 -SSLv3
76 |
77 | # SSL Cipher Suite:
78 | # List the ciphers that the client is permitted to negotiate.
79 | # See the mod_ssl documentation for a complete list.
80 | SSLCipherSuite HIGH:3DES:!aNULL:!MD5:!SEED:!IDEA
81 |
82 | # Speed-optimized SSL Cipher configuration:
83 | # If speed is your main concern (on busy HTTPS servers e.g.),
84 | # you might want to force clients to specific, performance
85 | # optimized ciphers. In this case, prepend those ciphers
86 | # to the SSLCipherSuite list, and enable SSLHonorCipherOrder.
87 | # Caveat: by giving precedence to RC4-SHA and AES128-SHA
88 | # (as in the example below), most connections will no longer
89 | # have perfect forward secrecy - if the server's key is
90 | # compromised, captures of past or future traffic must be
91 | # considered compromised, too.
92 | #SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5
93 | #SSLHonorCipherOrder on
94 |
95 | # Server Certificate:
96 | # Point SSLCertificateFile at a PEM encoded certificate. If
97 | # the certificate is encrypted, then you will be prompted for a
98 | # pass phrase. Note that a kill -HUP will prompt again. A new
99 | # certificate can be generated using the genkey(1) command.
100 | SSLCertificateFile /etc/pki/koji/kojidev.example.com.chain.crt
101 |
102 | # Server Private Key:
103 | # If the key is not combined with the certificate, use this
104 | # directive to point at the key file. Keep in mind that if
105 | # you've both a RSA and a DSA private key you can configure
106 | # both in parallel (to also allow the use of DSA ciphers, etc.)
107 | SSLCertificateKeyFile /etc/pki/koji/kojidev.example.com.key
108 |
109 | # Server Certificate Chain:
110 | # Point SSLCertificateChainFile at a file containing the
111 | # concatenation of PEM encoded CA certificates which form the
112 | # certificate chain for the server certificate. Alternatively
113 | # the referenced file can be the same as SSLCertificateFile
114 | # when the CA certificates are directly appended to the server
115 | # certificate for convinience.
116 | SSLCertificateChainFile /etc/pki/koji/kojidev.example.com.chain.crt
117 |
118 | # Certificate Authority (CA):
119 | # Set the CA certificate verification path where to find CA
120 | # certificates for client authentication or alternatively one
121 | # huge file containing all of them (file must be PEM encoded)
122 | SSLCACertificateFile /etc/pki/koji/koji-ca.crt
123 |
124 | # Client Authentication (Type):
125 | # Client certificate verification type and depth. Types are
126 | # none, optional, require and optional_no_ca. Depth is a
127 | # number which specifies how deeply to verify the certificate
128 | # issuer chain before deciding the certificate is not valid.
129 | #SSLVerifyClient require
130 | #SSLVerifyDepth 10
131 |
132 | # Access Control:
133 | # With SSLRequire you can do per-directory access control based
134 | # on arbitrary complex boolean expressions containing server
135 | # variable checks and other lookup directives. The syntax is a
136 | # mixture between C and Perl. See the mod_ssl documentation
137 | # for more details.
138 | #
139 | #SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \
140 | # and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \
141 | # and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \
142 | # and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \
143 | # and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \
144 | # or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/
145 | #
146 |
147 | # SSL Engine Options:
148 | # Set various options for the SSL engine.
149 | # o FakeBasicAuth:
150 | # Translate the client X.509 into a Basic Authorisation. This means that
151 | # the standard Auth/DBMAuth methods can be used for access control. The
152 | # user name is the `one line' version of the client's X.509 certificate.
153 | # Note that no password is obtained from the user. Every entry in the user
154 | # file needs this password: `xxj31ZMTZzkVA'.
155 | # o ExportCertData:
156 | # This exports two additional environment variables: SSL_CLIENT_CERT and
157 | # SSL_SERVER_CERT. These contain the PEM-encoded certificates of the
158 | # server (always existing) and the client (only existing when client
159 | # authentication is used). This can be used to import the certificates
160 | # into CGI scripts.
161 | # o StdEnvVars:
162 | # This exports the standard SSL/TLS related `SSL_*' environment variables.
163 | # Per default this exportation is switched off for performance reasons,
164 | # because the extraction step is an expensive operation and is usually
165 | # useless for serving static content. So one usually enables the
166 | # exportation for CGI and SSI requests only.
167 | # o StrictRequire:
168 | # This denies access when "SSLRequireSSL" or "SSLRequire" applied even
169 | # under a "Satisfy any" situation, i.e. when it applies access is denied
170 | # and no other module can change it.
171 | # o OptRenegotiate:
172 | # This enables optimized SSL connection renegotiation handling when SSL
173 | # directives are used in per-directory context.
174 | #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire
175 |
176 | SSLOptions +StdEnvVars
177 |
178 |
179 | SSLOptions +StdEnvVars
180 |
181 |
182 | # SSL Protocol Adjustments:
183 | # The safe and default but still SSL/TLS standard compliant shutdown
184 | # approach is that mod_ssl sends the close notify alert but doesn't wait for
185 | # the close notify alert from client. When you need a different shutdown
186 | # approach you can use one of the following variables:
187 | # o ssl-unclean-shutdown:
188 | # This forces an unclean shutdown when the connection is closed, i.e. no
189 | # SSL close notify alert is send or allowed to received. This violates
190 | # the SSL/TLS standard but is needed for some brain-dead browsers. Use
191 | # this when you receive I/O errors because of the standard approach where
192 | # mod_ssl sends the close notify alert.
193 | # o ssl-accurate-shutdown:
194 | # This forces an accurate shutdown when the connection is closed, i.e. a
195 | # SSL close notify alert is send and mod_ssl waits for the close notify
196 | # alert of the client. This is 100% SSL/TLS standard compliant, but in
197 | # practice often causes hanging connections with brain-dead browsers. Use
198 | # this only for browsers where you know that their SSL implementation
199 | # works correctly.
200 | # Notice: Most problems of broken clients are also related to the HTTP
201 | # keep-alive facility, so you usually additionally want to disable
202 | # keep-alive for those clients, too. Use variable "nokeepalive" for this.
203 | # Similarly, one has to force some clients to use HTTP/1.0 to workaround
204 | # their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and
205 | # "force-response-1.0" for this.
206 | BrowserMatch "MSIE [2-5]" \
207 | nokeepalive ssl-unclean-shutdown \
208 | downgrade-1.0 force-response-1.0
209 |
210 | # Per-Server Logging:
211 | # The home of a custom SSL log file. Use this when you want a
212 | # compact non-error SSL logfile on a virtual host basis.
213 | CustomLog logs/ssl_request_log \
214 | "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/roles/koji-hub/handlers/main.yml:
--------------------------------------------------------------------------------
1 | - name: restart httpd
2 | service:
3 | name: httpd
4 | state: restarted
5 |
--------------------------------------------------------------------------------
/roles/koji-hub/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Gather OS-specific variables
2 | include_vars: "{{ item }}"
3 | with_first_found:
4 | - "{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml"
5 | - "{{ ansible_distribution }}.yml"
6 |
7 | - name: Install SELinux packages
8 | package:
9 | name: "{{ selinux_packages }}"
10 | state: present
11 | register: selinux_packages_installed
12 |
13 | - name: re-gather facts after installing SELinux packages
14 | setup:
15 | when:
16 | - not ansible_selinux_python_present
17 | - selinux_packages_installed.changed
18 |
19 | - name: Install Koji packages
20 | package:
21 | name:
22 | - koji-hub
23 | - mod_ssl
24 | - mod_auth_gssapi
25 | state: present
26 |
27 | - name: allow httpd selinux access to write public_content_rw_t files
28 | seboolean:
29 | name: httpd_anon_write
30 | state: true
31 | persistent: true
32 | when:
33 | - ansible_selinux.status != "disabled"
34 | notify:
35 | - restart httpd
36 |
37 | - name: create koji user
38 | user:
39 | name: koji
40 | shell: /bin/bash
41 |
42 | - name: create postgres user koji
43 | postgresql_user:
44 | name: koji
45 | password: test
46 | # priv: "CONNECT/koji:ALL"
47 | # login_unix_socket: /var/run/postgresql
48 | become_user: postgres
49 |
50 | - name: create postgres koji database
51 | postgresql_db:
52 | name: koji
53 | owner: koji
54 | # login_unix_socket: /var/run/postgresql
55 | become_user: postgres
56 |
57 | - name: check if database for app needs populating
58 | postgresql_query:
59 | db: koji
60 | login_user: koji
61 | query: SELECT COUNT(*) FROM permissions;
62 | become_user: koji
63 | ignore_errors: true
64 | register: database_is_populated
65 |
66 | - name: populate the koji database
67 | shell: psql koji koji < /usr/share/doc/koji*/docs/schema.sql
68 | args:
69 | executable: /bin/bash
70 | when: database_is_populated.failed
71 | changed_when: true
72 | become_user: koji
73 |
74 | - name: insert kdreyer as a user
75 | postgresql_query:
76 | db: koji
77 | login_user: koji
78 | query: "INSERT INTO users (name, status, usertype) VALUES ('kdreyer', 0, 0);"
79 | when: database_is_populated.failed
80 | become_user: koji
81 |
82 | - name: grant kdreyer admin rights
83 | postgresql_query:
84 | db: koji
85 | login_user: koji
86 | query: "INSERT INTO user_perms (user_id, perm_id, creator_id) VALUES (1, 1, 1);"
87 | when: database_is_populated.failed
88 | become_user: koji
89 |
90 | - name: grant apache selinux access to HTTP keytab
91 | sefcontext:
92 | target: /var/local/HTTP.kojidev.example.com.keytab
93 | setype: httpd_config_t
94 | state: present
95 | when:
96 | - ansible_selinux.status != "disabled"
97 | notify:
98 | - restart httpd
99 |
100 | - name: grant apache read access to HTTP keytab
101 | file:
102 | path: /var/local/HTTP.kojidev.example.com.keytab
103 | owner: root
104 | group: apache
105 | mode: "0640"
106 | setype: httpd_config_t
107 | notify:
108 | - restart httpd
109 |
110 | - name: copy mod_ssl config
111 | copy:
112 | src: files/ssl.conf
113 | dest: /etc/httpd/conf.d/ssl.conf
114 | owner: root
115 | group: root
116 | mode: "0644"
117 | notify:
118 | - restart httpd
119 |
120 | - name: copy /etc/httpd/conf.d/kojihub.conf
121 | copy:
122 | src: files/kojihub.conf
123 | dest: /etc/httpd/conf.d/kojihub.conf
124 | owner: root
125 | group: root
126 | mode: "0644"
127 | notify:
128 | - restart httpd
129 |
130 | - name: copy /etc/koji-hub/hub.conf
131 | copy:
132 | src: files/hub.conf
133 | dest: /etc/koji-hub/hub.conf
134 | owner: root
135 | group: root
136 | mode: "0644"
137 | notify:
138 | - restart httpd
139 |
140 | - name: start httpd
141 | service:
142 | name: httpd
143 | enabled: true
144 | state: started
145 |
146 | - name: grant apache selinux access to /mnt/koji
147 | sefcontext:
148 | target: "/mnt/koji(/.*)?"
149 | setype: public_content_rw_t
150 | state: present
151 | when:
152 | - ansible_selinux.status != "disabled"
153 |
154 | - name: /mnt/koji directories
155 | file:
156 | path: /mnt/koji/{{ item }}
157 | state: directory
158 | mode: "0755"
159 | owner: apache
160 | group: apache
161 | setype: public_content_rw_t
162 | with_items:
163 | -
164 | - packages
165 | - repos
166 | - work
167 | - scratch
168 | - repos-dist
169 |
170 | - name: link our Koji CA to a public web-accessible location
171 | file:
172 | src: /etc/pki/koji/koji-ca.crt
173 | dest: /mnt/koji/koji-ca.crt
174 | state: link
175 |
176 | - name: Configure the new builder on the hub
177 | koji_host:
178 | koji: kojidev
179 | name: kojidev.example.com
180 | arches: [x86_64]
181 | state: enabled
182 | channels:
183 | - default
184 | - createrepo
185 | become: false
186 |
187 | - name: Configure the kojira user account
188 | koji_user:
189 | koji: kojidev
190 | name: kojira
191 | state: enabled
192 | permissions:
193 | - repo
194 | krb_principals:
195 | - koji/kojira@KOJIDEV.EXAMPLE.COM
196 | become: false
197 |
198 | - name: Configure the garbagecollector user account
199 | koji_user:
200 | koji: kojidev
201 | name: garbagecollector
202 | state: enabled
203 | permissions:
204 | - admin
205 | krb_principals:
206 | - koji/garbagecollector@KOJIDEV.EXAMPLE.COM
207 | become: false
208 |
209 | - name: trashcan tag
210 | koji_tag:
211 | koji: kojidev
212 | name: trashcan
213 | become: false
214 |
--------------------------------------------------------------------------------
/roles/koji-hub/vars/CentOS-7.yml:
--------------------------------------------------------------------------------
1 | RedHat-7.yml
--------------------------------------------------------------------------------
/roles/koji-hub/vars/CentOS-8.yml:
--------------------------------------------------------------------------------
1 | RedHat-8.yml
--------------------------------------------------------------------------------
/roles/koji-hub/vars/RedHat-7.yml:
--------------------------------------------------------------------------------
1 | selinux_packages:
2 | - libsemanage-python
3 | - policycoreutils-python
4 |
--------------------------------------------------------------------------------
/roles/koji-hub/vars/RedHat-8.yml:
--------------------------------------------------------------------------------
1 | selinux_packages:
2 | - python3-libsemanage
3 | - python3-policycoreutils
4 |
--------------------------------------------------------------------------------
/roles/koji-hub/vars/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
--------------------------------------------------------------------------------
/roles/koji-ra/files/kojira.conf:
--------------------------------------------------------------------------------
1 | [kojira]
2 | ; The URL for the koji hub server
3 | server=https://kojidev.example.com/kojihub
4 |
5 | ; The directory containing the repos/ directory
6 | topdir=/mnt/koji
7 |
8 | ; Logfile
9 | logfile=/var/log/kojira.log
10 |
11 | ;configuration for Kerberos authentication
12 |
13 | ;the kerberos principal to use
14 | principal=koji/kojira@KOJIDEV.EXAMPLE.COM
15 |
16 | ;location of the keytab
17 | keytab = /var/local/koji.kojira.keytab
18 |
19 | ;configuration for SSL authentication
20 |
21 | ;client certificate
22 | ;cert = /etc/kojira/client.crt
23 |
24 | ;certificate of the CA that issued the HTTP server certificate
25 | ;serverca = /etc/kojira/serverca.crt
26 |
27 | ;how soon (in seconds) to clean up expired repositories. 1 week default
28 | ;deleted_repo_lifetime = 604800
29 |
30 | ;how soon (in seconds) to clean up dist repositories. 1 week default here too
31 | ;dist_repo_lifetime = 604800
32 |
33 | ;turn on debugging statements in the log
34 | ;debug = false
35 |
36 | ; ignored repositories according to glob. Multiple masks separated by space.
37 | ; ignore_tags =
38 |
39 |
40 | ; Monitor external repos and trigger the appropriate Koji repo regenerations
41 | ; when they change. Note that you need to have your database set to use UTC,
42 | ; as otherwise you can end with weird behaviour. For details see
43 | ; https://pagure.io/koji/issue/2159
44 | ; check_external_repos = false
45 |
--------------------------------------------------------------------------------
/roles/koji-ra/handlers/main.yml:
--------------------------------------------------------------------------------
1 | - name: restart kojira
2 | service:
3 | name: kojira
4 | state: restarted
5 |
--------------------------------------------------------------------------------
/roles/koji-ra/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Add yum repository to work around rhbz1661580
2 | yum_repository:
3 | name: bz1661580
4 | description: work around rhbz1661580
5 | baseurl: https://fedorapeople.org/~ktdreyer/bz1661580/
6 | gpgcheck: false
7 | when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int == 7
8 |
9 | - name: Install the very latest requests-kerberos (rhbz1661580)
10 | package:
11 | name:
12 | - python-requests-kerberos
13 | - python2-kerberos
14 | state: latest # noqa package-latest
15 | when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int == 7
16 | retries: 100
17 | delay: 3
18 |
19 | - name: Install Koji utils package
20 | package:
21 | name: koji-utils
22 | state: present
23 |
24 | - name: copy /etc/kojira/kojira.conf
25 | copy:
26 | src: files/kojira.conf
27 | dest: /etc/kojira/kojira.conf
28 | owner: root
29 | group: root
30 | mode: "0644"
31 | notify:
32 | - restart kojira
33 |
34 | - name: start kojira
35 | service:
36 | name: kojira
37 | enabled: true
38 | state: started
39 |
--------------------------------------------------------------------------------
/roles/koji-ssl-admin/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Gather OS-specific variables
2 | include_vars: "{{ item }}"
3 | with_first_found:
4 | - "{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml"
5 | - "{{ ansible_distribution }}.yml"
6 |
7 | - name: install prerequisites
8 | package:
9 | name: "{{ ssl_admin_prerequisites }}"
10 | state: present
11 |
12 | - name: clone koji-tools.git (rhel 7 compatible) # noqa latest[git]
13 | git:
14 | repo: https://pagure.io/koji-tools.git
15 | dest: /usr/local/koji-tools
16 | # RHEL 7 has Git v1.8.3.1, so it cannot do shallow clones with "depth: 1"
17 | when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int == 7
18 |
19 | - name: clone koji-tools.git # noqa latest[git]
20 | git:
21 | repo: https://pagure.io/koji-tools.git
22 | dest: /usr/local/koji-tools
23 | depth: 1
24 | when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int > 7
25 |
26 | - name: /etc/pki/koji directory
27 | file:
28 | path: /etc/pki/koji
29 | state: directory
30 | mode: "0755"
31 |
32 | - name: Create a Koji SSL CA
33 | command:
34 | argv:
35 | - /usr/local/koji-tools/src/bin/koji-ssl-admin
36 | - new-ca
37 | - --common-name
38 | - Test Koji CA
39 | creates: /etc/pki/koji/koji-ca.crt
40 | chdir: /etc/pki/koji/
41 |
42 | - name: Create a Server CSR
43 | command:
44 | argv:
45 | - /usr/local/koji-tools/src/bin/koji-ssl-admin
46 | - server-csr
47 | - kojidev.example.com
48 | creates: /etc/pki/koji/kojidev.example.com.csr
49 | chdir: /etc/pki/koji/
50 |
51 | - name: Sign Server CSR
52 | command:
53 | argv:
54 | - /usr/local/koji-tools/src/bin/koji-ssl-admin
55 | - sign
56 | - kojidev.example.com.csr
57 | creates: /etc/pki/koji/kojidev.example.com.chain.crt
58 | chdir: /etc/pki/koji/
59 |
--------------------------------------------------------------------------------
/roles/koji-ssl-admin/vars/CentOS-7.yml:
--------------------------------------------------------------------------------
1 | RedHat-7.yml
--------------------------------------------------------------------------------
/roles/koji-ssl-admin/vars/CentOS-8.yml:
--------------------------------------------------------------------------------
1 | RedHat-8.yml
--------------------------------------------------------------------------------
/roles/koji-ssl-admin/vars/RedHat-7.yml:
--------------------------------------------------------------------------------
1 | ssl_admin_prerequisites:
2 | - git-core
3 | - python3
4 | - python36-cryptography
5 | - python36-dateutil
6 |
--------------------------------------------------------------------------------
/roles/koji-ssl-admin/vars/RedHat-8.yml:
--------------------------------------------------------------------------------
1 | ssl_admin_prerequisites:
2 | - git-core
3 | - python3
4 | - python3-cryptography
5 | - python3-dateutil
6 |
--------------------------------------------------------------------------------
/roles/koji-web/files/kojiweb.conf:
--------------------------------------------------------------------------------
1 | #We use wsgi by default
2 | Alias /koji "/usr/share/koji-web/scripts/wsgi_publisher.py"
3 | #(configuration goes in /etc/kojiweb/web.conf)
4 |
5 |
6 | Options ExecCGI
7 | SetHandler wsgi-script
8 | WSGIApplicationGroup %{GLOBAL}
9 | # ^ works around an OpenSSL issue
10 | # see: https://cryptography.io/en/latest/faq/#starting-cryptography-using-mod-wsgi-produces-an-internalerror-during-a-call-in-register-osrandom-engine
11 |
12 | Order allow,deny
13 | Allow from all
14 |
15 | = 2.4>
16 | Require all granted
17 |
18 |
19 |
20 | # uncomment this to enable authentication via Kerberos
21 |
22 | AuthType GSSAPI
23 | AuthName "Koji Web UI"
24 | GssapiCredStore keytab:/var/local/HTTP.kojidev.example.com.keytab
25 | Require valid-user
26 | ErrorDocument 401 /koji-static/errors/unauthorized.html
27 |
28 |
29 | # uncomment this to enable authentication via SSL client certificates
30 | #
31 | # SSLVerifyClient require
32 | # SSLVerifyDepth 10
33 | # SSLOptions +StdEnvVars
34 | #
35 |
36 | Alias /koji-static/ "/usr/share/koji-web/static/"
37 |
38 |
39 | Options None
40 | AllowOverride None
41 |
42 | Order allow,deny
43 | Allow from all
44 |
45 | = 2.4>
46 | Require all granted
47 |
48 |
49 |
--------------------------------------------------------------------------------
/roles/koji-web/files/web.conf:
--------------------------------------------------------------------------------
1 | [web]
2 | SiteName = koji
3 | # KojiTheme = mytheme
4 |
5 | # Key urls
6 | KojiHubURL = https://kojidev.example.com/kojihub
7 | KojiFilesURL = https://kojidev.example.com/kojifiles
8 |
9 | # Kerberos authentication options
10 | WebPrincipal = koji/kojiweb@KOJIDEV.EXAMPLE.COM
11 | WebKeytab = /var/local/koji.kojiweb.keytab
12 | WebCCache = /var/tmp/kojiweb.ccache
13 | # The service name of the principal being used by the hub
14 | # KrbService = host
15 |
16 | # SSL authentication options
17 | #WebCert = /etc/kojiweb/kojiweb.cert
18 | KojiHubCA = /etc/pki/koji/koji-ca.crt
19 |
20 | LoginTimeout = 72
21 |
22 | # This must be changed and uncommented before deployment
23 | # Secret = CHANGE_ME
24 | Secret = f447905c-9314-43dc-acdb-f1e5ddfc63fa
25 |
26 | LibPath = /usr/share/koji-web/lib
27 |
28 | # If set to True, then the footer will be included literally.
29 | # If False, then the footer will be included as another Kid Template.
30 | # Defaults to True
31 | LiteralFooter = True
32 |
33 | # This can be a space-delimited list of the numeric IDs of users that you want
34 | # to hide from tasks listed on the front page. You might want to, for instance,
35 | # hide the activity of an account used for continuous integration.
36 | # HiddenUsers = 5372 1234
37 |
38 | # Task types visible in pulldown menu on tasks page.
39 | # Tasks =
40 | # runroot plugin provided via main package could be listed as:
41 | # Tasks = runroot
42 | # Tasks that can exist without a parent
43 | # ToplevelTasks =
44 | # Tasks that can have children
45 | # ParentTasks =
46 |
47 | # Uncommenting this will show python tracebacks in the webUI, but they are the
48 | # same as what you will see in apache's error_log.
49 | # Not for production use
50 | # PythonDebug = True
51 |
--------------------------------------------------------------------------------
/roles/koji-web/handlers/main.yml:
--------------------------------------------------------------------------------
1 | - name: restart httpd
2 | service:
3 | name: httpd
4 | state: restarted
5 |
--------------------------------------------------------------------------------
/roles/koji-web/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Install Koji web packages
2 | package:
3 | name: koji-web
4 | state: present
5 |
6 | - name: copy /etc/httpd/conf.d/kojiweb.conf
7 | copy:
8 | src: files/kojiweb.conf
9 | dest: /etc/httpd/conf.d/kojiweb.conf
10 | owner: root
11 | group: root
12 | mode: "0644"
13 | notify:
14 | - restart httpd
15 |
16 | - name: copy /etc/kojiweb/web.conf
17 | copy:
18 | src: files/web.conf
19 | dest: /etc/kojiweb/web.conf
20 | owner: root
21 | group: root
22 | mode: "0644"
23 | notify:
24 | - restart httpd
25 |
26 | - name: grant apache selinux access to kojiweb keytab
27 | sefcontext:
28 | target: /var/local/koji.kojiweb.keytab
29 | setype: httpd_config_t
30 | state: present
31 | when:
32 | - ansible_selinux.status != "disabled"
33 | notify:
34 | - restart httpd
35 |
36 | - name: grant apache read access to kojiweb keytab
37 | file:
38 | path: /var/local/koji.kojiweb.keytab
39 | owner: root
40 | group: apache
41 | mode: "0640"
42 | setype: httpd_config_t
43 | notify:
44 | - restart httpd
45 |
46 | - name: allow httpd selinux access to contact kerberos and koji-hub
47 | seboolean:
48 | name: httpd_can_network_connect
49 | state: true
50 | persistent: true
51 | when:
52 | - ansible_selinux.status != "disabled"
53 | notify:
54 | - restart httpd
55 |
--------------------------------------------------------------------------------
/roles/postgresql/handlers/main.yml:
--------------------------------------------------------------------------------
1 | - name: reload postgresql
2 | service:
3 | name: postgresql
4 | state: reloaded
5 |
--------------------------------------------------------------------------------
/roles/postgresql/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Install postgresql-server package
2 | package:
3 | name: postgresql-server
4 | state: present
5 |
6 | - name: initialize postgresql
7 | command: postgresql-setup initdb
8 | args:
9 | creates: /var/lib/pgsql/data/pg_hba.conf
10 | notify:
11 | - reload postgresql
12 |
13 | - name: configure pg_hba.conf
14 | copy:
15 | dest: /var/lib/pgsql/data/pg_hba.conf
16 | content: |
17 | local koji koji trust
18 | local all postgres peer
19 | mode: preserve
20 | notify:
21 | - reload postgresql
22 |
23 | - name: disable TCP/IP for postgres
24 | lineinfile:
25 | dest: /var/lib/pgsql/data/postgresql.conf
26 | regexp: '^#listen_addresses'
27 | line: "listen_addresses = ''"
28 | notify:
29 | - reload postgresql
30 |
31 | # Note: this is not in the upstream documentation. It's still under discussion
32 | # upstream, see
33 | # https://lists.fedorahosted.org/archives/list/koji-devel@lists.fedorahosted.org/thread/NMDIDYS7CZWB3SMPT6UO2P5WGZXKIZVW/
34 | - name: increase number of max connections
35 | lineinfile:
36 | dest: /var/lib/pgsql/data/postgresql.conf
37 | regexp: '^max_connections'
38 | line: "max_connections = 500"
39 | notify:
40 | - reload postgresql
41 | when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int > 7
42 |
43 | - name: start postgresql
44 | service:
45 | name: postgresql
46 | state: started
47 | enabled: true
48 |
--------------------------------------------------------------------------------
/roles/rabbitmq/files/rabbitmq.conf:
--------------------------------------------------------------------------------
1 | # Basic SSL configuration:
2 | listeners.ssl.default = 5671
3 | ssl_options.certfile = /etc/pki/koji/kojidev.example.com.chain.crt
4 | ssl_options.keyfile = /etc/pki/koji/kojidev.example.com.key
5 | ssl_options.cacertfile = /etc/pki/koji/koji-ca.crt
6 | ssl_options.verify = verify_peer
7 | ssl_options.fail_if_no_peer_cert = true
8 |
9 | # Enable mTLS auth:
10 | auth_backends.1 = rabbit_auth_backend_internal
11 | auth_mechanisms.1 = PLAIN
12 | auth_mechanisms.2 = EXTERNAL
13 | # TODO: change this to "UID"?
14 | ssl_cert_login_from = common_name
15 |
16 | # Secure the management interface on TCP 15671:
17 | management.ssl.port = 15671
18 | management.ssl.certfile = /etc/pki/koji/kojidev.example.com.chain.crt
19 | management.ssl.keyfile = /etc/pki/koji/kojidev.example.com.key
20 | management.ssl.cacertfile = /etc/pki/koji/koji-ca.crt
21 |
22 | # Disable non-TLS connections:
23 | listeners.tcp = none
24 | # management.tcp = none is an invalid config. maybe it doesn't listen to HTTP
25 | # by default?
26 | # management.tcp = none
27 |
--------------------------------------------------------------------------------
/roles/rabbitmq/handlers/main.yml:
--------------------------------------------------------------------------------
1 | - name: restart rabbitmq-server
2 | service:
3 | name: rabbitmq-server
4 | state: restarted
5 |
--------------------------------------------------------------------------------
/roles/rabbitmq/tasks/main.yml:
--------------------------------------------------------------------------------
1 | # https://github.com/fedora-selinux/selinux-policy/pull/1053
2 | - name: Allow RabbitMQ to listen on tcp port 15671
3 | seport:
4 | ports: 15671
5 | proto: tcp
6 | setype: rabbitmq_port_t
7 | state: present
8 |
9 | - name: install centos repo for rabbitmq
10 | package:
11 | name: centos-release-rabbitmq-38
12 | state: present
13 |
14 | - name: enable powertools
15 | ini_file:
16 | path: /etc/yum.repos.d/CentOS-Stream-PowerTools.repo
17 | section: powertools
18 | option: enabled
19 | value: "1"
20 | owner: root
21 | group: root
22 | mode: "0644"
23 |
24 | - name: install rabbitmq-server
25 | package:
26 | name: rabbitmq-server
27 | state: present
28 |
29 | - name: copy /etc/rabbitmq/rabbitmq.conf
30 | copy:
31 | src: files/rabbitmq.conf
32 | dest: /etc/rabbitmq/rabbitmq.conf
33 | owner: root
34 | group: root
35 | mode: "0644"
36 | notify:
37 | - restart rabbitmq-server
38 |
39 | - name: enable rabbitmq plugins
40 | rabbitmq_plugin:
41 | names: rabbitmq_management,rabbitmq_auth_mechanism_ssl,rabbitmq_amqp1_0
42 | state: enabled
43 | notify:
44 | - restart rabbitmq-server
45 |
46 | - name: start rabbitmq-server
47 | service:
48 | name: rabbitmq-server
49 | enabled: true
50 | state: started
51 |
52 | - name: remove rabbitmq guest user
53 | rabbitmq_user:
54 | user: guest
55 | state: absent
56 |
57 | - name: add rabbitmq koji user
58 | rabbitmq_user:
59 | user: msg-producer-koji
60 | vhost: /
61 | configure_priv: .*
62 | read_priv: .*
63 | write_priv: .*
64 | state: present
65 |
66 | - name: add read-only rabbitmq kdreyer user
67 | rabbitmq_user:
68 | user: kdreyer
69 | vhost: /
70 | configure_priv: ^$
71 | read_priv: .*
72 | write_priv: ^$
73 | state: present
74 |
--------------------------------------------------------------------------------
/setup-koji.yml:
--------------------------------------------------------------------------------
1 | - name: manage Koji VM
2 | hosts: all
3 | become: yes
4 | become_method: sudo
5 | roles:
6 | - kdc
7 | - koji-ssl-admin
8 | - koji-client
9 | - postgresql
10 | - koji-hub
11 | - koji-web
12 | - koji-builder
13 | - koji-ra
14 | - koji-gc
15 |
--------------------------------------------------------------------------------
/vagrant/README.rst:
--------------------------------------------------------------------------------
1 | Bring up a new server::
2 |
3 | vagrant up --no-destroy-on-error
4 |
5 | Re-run the Ansible playbooks (if they failed, or if you changed something)::
6 |
7 | vagrant provision
8 |
9 | Log in with SSH::
10 |
11 | vagrant ssh
12 |
13 | Destroy the VM and disk::
14 |
15 | vagrant destroy
16 |
17 | Pull down an updated CentOS image from Vagrant Cloud::
18 |
19 | vagrant box update
20 |
21 | Or use a centos.org snapshot::
22 |
23 | vagrant box add --name centos/stream8 -f https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-Vagrant-8-20230925.0.x86_64.vagrant-libvirt.box
24 |
25 | Note that you must destroy and rebuild your Vagrant VMs to make use of this newer image::
26 |
27 | vagrant destroy
28 | vagrant up
29 |
30 | Helpful Documentation:
31 |
32 | * https://docs.ansible.com/ansible/latest/scenario_guides/guide_vagrant.html
33 | * https://www.vagrantup.com/docs/provisioning/ansible.html
34 | * https://www.vagrantup.com/docs/provisioning/ansible_common.html
35 |
--------------------------------------------------------------------------------
/vagrant/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 |
3 | ENV['VAGRANT_DEFAULT_PROVIDER'] = 'libvirt'
4 |
5 | Vagrant.configure("2") do |config|
6 | # Updates sort of lag here, see https://bugs.centos.org/view.php?id=18028
7 | config.vm.box = "centos/stream8"
8 | config.vm.box_check_update = false
9 |
10 | config.vm.provision "ansible" do |ansible|
11 | ansible.playbook = "vagrant.yml"
12 | end
13 |
14 | config.vm.provider :libvirt do |virt|
15 | virt.storage :file, :size => '20G', :device => 'vdb'
16 | virt.memory = 4096
17 | virt.cpus = 2
18 | virt.qemu_use_session = false
19 | end
20 |
21 | config.ssh.forward_agent = true
22 |
23 | config.vm.define :kojidev do |node|
24 | node.vm.hostname = 'kojidev'
25 | end
26 |
27 | # This creates a second NIC, which I do not want:
28 | # config.vm.network :private_network, :ip => '192.168.121.100'
29 | # Also there seems to be a bug with wrapping the network settings in a
30 | # "provider" block:
31 | # https://github.com/vagrant-libvirt/vagrant-libvirt/issues/1165
32 |
33 | end
34 |
--------------------------------------------------------------------------------
/vagrant/reset-database.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -eux
3 |
4 | # Reset the Koji database to an empty state.
5 | # Useful for starting over when applying large configurations.
6 |
7 | systemctl stop httpd
8 |
9 | sudo -u postgres psql -c 'DROP DATABASE IF EXISTS koji;'
10 | sudo -u postgres psql -c 'DROP USER IF EXISTS koji;'
11 | sudo -u postgres psql -c 'CREATE DATABASE koji;'
12 | sudo -u postgres psql -c "CREATE USER koji WITH ENCRYPTED PASSWORD 'koji';"
13 | sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE koji TO koji;"
14 |
15 | psql -q koji koji < /usr/share/doc/koji/docs/schema.sql
16 |
17 | psql -U koji -c "INSERT INTO users (name, status, usertype) VALUES ('kdreyer', 0, 0);"
18 | psql -U koji -c "INSERT INTO user_perms (user_id, perm_id, creator_id) VALUES (1, 1, 1);"
19 |
20 | systemctl start httpd
21 |
--------------------------------------------------------------------------------
/vagrant/roles:
--------------------------------------------------------------------------------
1 | ../roles/
--------------------------------------------------------------------------------
/vagrant/vagrant.yml:
--------------------------------------------------------------------------------
1 | - name: manage Koji Vagrant VM
2 | hosts: all
3 | become: yes
4 | become_method: sudo
5 | pre_tasks:
6 | - name: Install EPEL repo
7 | yum:
8 | name: epel-release
9 | state: present
10 | - name: create filesystem on /dev/vdb
11 | filesystem:
12 | fstype: ext4
13 | dev: /dev/vdb
14 | - name: mount /dev/vdb
15 | mount:
16 | fstype: ext4
17 | src: /dev/vdb
18 | path: /mnt
19 | state: mounted
20 | - name: create /mnt/mock directory
21 | file:
22 | path: /mnt/mock
23 | state: directory
24 | mode: 0755
25 | - name: symlink /var/lib/mock to /mnt/mock
26 | file:
27 | src: /mnt/mock
28 | dest: /var/lib/mock
29 | state: link
30 | roles:
31 | - kdc
32 | - koji-ssl-admin
33 | - koji-client
34 | - postgresql
35 | - koji-hub
36 | - koji-web
37 | - koji-builder
38 | - koji-ra
39 | - koji-gc
40 |
--------------------------------------------------------------------------------