commit 36dc3a0ec0bdd0cf41d171d961c411480124bc8c Author: Louis Abel Date: Wed Mar 1 19:10:11 2023 -0700 init diff --git a/.ansible-lint b/.ansible-lint new file mode 100644 index 0000000..5efe51a --- /dev/null +++ b/.ansible-lint @@ -0,0 +1,6 @@ +# .ansible-lint +warn_list: + - '204' # Lines should be less than 160 characters + - 'meta-no-info' +skip_list: + - 'role-name' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f64d238 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-added-large-files + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-json + - id: pretty-format-json + - id: detect-private-key + + - repo: local + hooks: + - id: ansible-lint + name: Ansible-lint + description: This hook runs ansible-lint. + entry: ansible-lint -v --force-color + language: python + # do not pass files to ansible-lint, see: + # https://github.com/ansible/ansible-lint/issues/611 + pass_filenames: false + always_run: true + additional_dependencies: + - ansible-core>=2.13.3 + + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.29.0 + hooks: + - id: yamllint + files: \.(yaml|yml)$ + types: [file, yaml] + entry: yamllint diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..4a8ae86 --- /dev/null +++ b/.yamllint @@ -0,0 +1,11 @@ +--- +extends: default + +rules: + line-length: + max: 140 + level: warning + +ignore: | + .travis.yml + .github diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..60554f6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +- what do we need to do + +## [0.0.1] - 2020-12-13 +### Added +- Initial + +### Changed +- None + +### Removed +- None diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4330a18 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Louis Abel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..df596e8 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Ansible template role: Ipsilon +Role that fulfills some need (ipsilon )and there was not some sort of equivalent elsewhere. diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..1b43bd2 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,53 @@ +--- +# Defaults variables for role ipsilon +ipsilon_db_user: ipsilon +ipsilon_db_password: ThisIsNotThePassword +ipsilon_db_host: dbhost +ipsilon_db_name: ipsilon_db +idp_hostname: accounts.rockylinux.org + +# The list of ipsilon packages +ipsilon_packages: + - ipsilon + - ipsilon-authform + - ipsilon-base + - ipsilon-filesystem + - ipsilon-infofas + - ipsilon-infosssd + - ipsilon-openid + - ipsilon-openidc + - ipsilon-saml2 + - ipsilon-saml2-base + - ipsilon-tools-ipa + - python3-setuptools + - python3-freeipa + - httpd + - mod_ssl + - mod_auth_openidc + - mod_auth_gssapi + - git + +ipsilon_booleans: + - httpd_can_network_connect + - httpd_can_network_connect_db + - httpd_dbus_sssd + - httpd_tmp_exec + - httpd_mod_auth_patm + +ipsilon_openid_trusted_roots: + - https://git.rockylinux.org/ + - https://vote.rockylinux.org/ + - https://lists.resf.org/ + +ipsilon_openidc_scopes: + - ipsilon + - rocky + +ipsilon_openidc_custom_scopes: + - rocky + +ipsilon_fas_username: userman +ipsilon_fas_password: ThisIsNotThePassword +ipsilon_oidc_salt: data +ipsilon_saml2_salt: data +... diff --git a/files/generate-oidc-token b/files/generate-oidc-token new file mode 100644 index 0000000..28e2272 --- /dev/null +++ b/files/generate-oidc-token @@ -0,0 +1,97 @@ +#!/usr/bin/python3 +# Copyright (c) 2018 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" +This script will accept some parameters and will print out some SQL you can run against the Ipsilon +database, and a token you can give to an application to authenticate against a service. +""" +import base64 +import json +import os +import time +import uuid + +import click + + +secret = base64.urlsafe_b64encode(os.urandom(64))[:64].decode() + + +template = """ +Run this SQL against Ipsilon's database: + +--------START CUTTING HERE-------- +BEGIN; +insert into token values ('{uuid}','username','{service_name}@service'); +insert into token values ('{uuid}','security_check','{secret}'); +insert into token values ('{uuid}','client_id','{service_name}'); +insert into token values ('{uuid}','expires_at','{expiration}'); +insert into token values ('{uuid}','type','Bearer'); +insert into token values ('{uuid}','issued_at','{now}'); +insert into token values ('{uuid}','scope','{scope}'); +COMMIT; +-------- END CUTTING HERE -------- + +""" + + +def validate_scopes(ctx, param, scopes): + """ + Ensure that the user provided at least one scope. + + Args: + ctx(click.core.Context): Unused. + param (click.core.Option): Unused. + scopes (tuple): The scopes provided by the user that we are validating. + Raises: + click.BadParameter: If the length of the scopes tuple is less than 1. + """ + if len(scopes) < 1: + raise click.BadParameter('At least one scope must be provided.') + + return scopes + + +@click.command() +@click.argument('service_name') +@click.option('--expiration', '-e', prompt='Number of days until expiration', type=int, + help='The number of days from now until this token expires.') +@click.option('--scope', '-s', multiple=True, callback=validate_scopes, + help='A scope to include for this token. May be supplied multiple times.') +@click.option('--no-openid', is_flag=True, help='Do not use "openid" as the first item in scope.') +def generate_token(service_name, expiration, scope, no_openid): + """ + Print out SQL to insert a token in the Ipsilon database, and the token itself. + + SERVICE_NAME is the name of the service that the token will be used by, (e.g., bodhi). + """ + identifier = uuid.uuid4() + + now = int(time.time()) + expiration = now + (expiration * 24 * 3600) + + scope = list(scope) + if not no_openid: + scope.insert(0, 'openid') + scope = json.dumps(scope) + + print(template.format(uuid=identifier, service_name=service_name, secret=secret, + expiration=expiration, scope=scope, now=now)) + + print("Token: {}_{}\n".format(identifier, secret)) + + +if __name__ == '__main__': + generate_token() diff --git a/files/genkey.py b/files/genkey.py new file mode 100644 index 0000000..5138728 --- /dev/null +++ b/files/genkey.py @@ -0,0 +1,18 @@ +#!/usr/bin/python3 +import time +import os.path + +from jwcrypto.jwk import JWK, JWKSet + +keyid = int(time.time()) +keyset = JWKSet() +rsasig = JWK(generate='RSA', size=2048, use='sig', + kid='%s-sig' % keyid) +keyset.add(rsasig) +rsasig = JWK(generate='RSA', size=2048, use='enc', + kid='%s-enc' % keyid) +keyset.add(rsasig) + +with open('/etc/ipsilon/openidc.key', 'w') as m: + m.write(keyset.export()) + diff --git a/files/rocky.py b/files/rocky.py new file mode 100644 index 0000000..d690b91 --- /dev/null +++ b/files/rocky.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import + +from ipsilon.providers.openidc.plugins.common import OpenidCExtensionBase + + +class OpenidCExtension(OpenidCExtensionBase): + name = 'rocky' + display_name = 'Rocky Tokens' + scopes = { + 'openid': { + 'display_name': 'openid', + }, + 'profile': { + 'display_name': 'profile', + }, + 'email': { + 'display_name': 'email', + }, + 'address': { + 'display_name': 'address', + }, + 'phone': { + 'display_name': 'phone', + }, + 'https://mbs.rockylinux.org/oidc/mbs-submit-build': { + 'display_name': 'mbs', + }, + 'https://id.fedoraproject.org/scope/groups': { + 'display_name': 'groups', + } + } diff --git a/files/rocky_logo.png b/files/rocky_logo.png new file mode 100644 index 0000000..14acdf7 Binary files /dev/null and b/files/rocky_logo.png differ diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..a976749 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,11 @@ +--- +# Defaults handlers for role ipsilon +- name: restart_sssd + ansible.builtin.service: + name: sssd + state: restarted + +- name: reload_httpd + ansible.builtin.service: + name: httpd + state: reloaded diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..568c597 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,53 @@ +galaxy_info: + author: your name + description: your role description + company: your company (optional) + + # If the issue tracker for your role is not on github, uncomment the + # next line and provide a value + # issue_tracker_url: http://example.com/issue/tracker + + # Choose a valid license ID from https://spdx.org - some suggested licenses: + # - BSD-3-Clause (default) + # - MIT + # - GPL-2.0-or-later + # - GPL-3.0-only + # - Apache-2.0 + # - CC-BY-4.0 + license: MIT + + min_ansible_version: 2.8 + + # If this a Container Enabled role, provide the minimum Ansible Container version. + # min_ansible_container_version: + + # + # Provide a list of supported platforms, and for each platform a list of versions. + # If you don't wish to enumerate all versions for a particular platform, use 'all'. + # To view available platforms and versions (or releases), visit: + # https://galaxy.ansible.com/api/v1/platforms/ + # + # platforms: + # - name: Fedora + # versions: + # - all + # - 25 + # - name: SomePlatform + # versions: + # - all + # - 1.0 + # - 7 + # - 99.99 + + galaxy_tags: [] + # List tags for your role here, one per line. A tag is a keyword that describes + # and categorizes the role. Users find roles by searching for tags. Be sure to + # remove the '[]' above, if you add tags to this list. + # + # NOTE: A tag is limited to a single word comprised of alphanumeric characters. + # Maximum 20 tags per role. + +dependencies: [] + # List your role dependencies here, one per line. Be sure to remove the '[]' above, + # if you add dependencies to this list. + \ No newline at end of file diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..82b0121 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,102 @@ +--- +- name: Install required packages + ansible.builtin.dnf: + name: "{{ ipsilon_packages }}" + state: installed + tags: + - packages + +- name: Initialize ipsilon if needed + ansible.builtin.command: > + ipsilon-server-install + --secure yes + --openid yes + --openidc yes + --saml2 yes + --ipa yes + --info-sssd yes + --form yes + --gssapi-httpd-keytab /etc/httpd.keytab + --admin-user admin + --hostname {{ idp_hostname }} + args: + creates: /etc/ipsilon/idp/ipsilon.conf + no_log: true + register: ipsilon_install + tags: + - init + +- name: Set booleans + ansible.posix.seboolean: + name: "{{ item }}" + state: true + persistent: true + loop: "{{ ipsilon_booleans }}" + tags: + - selinux + +- name: Branding ipsilon with logo + ansible.builtin.copy: + src: rocky_logo.png + dest: "{{ item }}" + owner: root + group: root + mode: '0644' + with_items: + - /usr/share/ipsilon/ui/img/brand-lg.png + - /usr/share/ipsilon/ui/img/brand.png + +- name: Distributing openidc genkey script + ansible.builtin.copy: + src: genkey.py + dest: /var/lib/ipsilon/idp/genkey.py + mode: '0755' + owner: ipsilon + group: ipsilon + +- name: Generate oidc token script + ansible.builtin.copy: + src: generate-oidc-token + dest: /var/lib/ipsilon/idp/generate-oidc-token + mode: '0755' + owner: ipsilon + group: ipsilon + +- name: Distribute ipsilon.conf + ansible.builtin.template: + src: "ipsilon.conf" + dest: /etc/ipsilon/idp/ipsilon.conf + owner: ipsilon + group: ipsilon + mode: 0600 + tags: + - config + notify: + - restart_httpd + +- name: Distribute configuration.conf + ansible.builtin.template: + src: "configuration.conf" + dest: /etc/ipsilon/idp/configuration.conf + owner: ipsilon + group: ipsilon + mode: 0600 + tags: + - config + notify: + - restart_httpd + +- name: Configure SSSD part 1 + ansible.builtin.replace: + path: /etc/sssd/sssd.conf + regexp: ^ldap_user_extra_attrs = [\w,\s]+$ + replace: ldap_user_extra_attrs = mail, street, locality, st, postalCode, telephoneNumber, givenname, sn, fasTimeZone, fasLocale, fasIRCNick, fasGPGKeyId, fasCreationTime, fasStatusNote, fasRHBZEmail, fasGitHubUsername, fasGitLabUsername, fasWebsiteURL, fasIsPrivate, ipaSshPubKey + notify: restart_sssd + +- name: Configure SSSD part 2 + ansible.builtin.replace: + path: /etc/sssd/sssd.conf + regexp: ^user_attributes = [\w,\s]+$ + replace: user_attributes = +mail, +street, +locality, +st, +postalCode, +telephoneNumber, +givenname, +sn, +fasTimeZone, +fasLocale, +fasIRCNick, +fasGPGKeyId, +fasCreationTime, +fasStatusNote, +fasRHBZEmail, +fasGitHubUsername, +fasGitLabUsername, +fasWebsiteURL, +fasIsPrivate, +ipaSshPubKey + notify: restart_sssd +... diff --git a/templates/configuration.conf b/templates/configuration.conf new file mode 100644 index 0000000..26ee8df --- /dev/null +++ b/templates/configuration.conf @@ -0,0 +1,74 @@ +[info_config] +sssd preconfigured = True +global enabled = sssd,nss,fas +fas fas url = https://accounts.rockylinux.org/ +fas fas proxy client user agent = Ipsilon v1.0 +fas fas insecure auth = False +fas bind username = {{ ipsilon_fas_username }} +fas bind password = {{ ipsilon_fas_password }} +fas preconfigured=True + +[sssd_data] + +[nss_data] + +[fas_data] + +[login_config] +fas fas url = https://accounts.rockylinux.org +fas fas proxy client user agent = Ipsilon v1.0 +fas fas insecure auth = False +fas username text = RAS Username +fas password text = Password +fas help text = Login with your RAS credentials +global enabled = gssapi,form + +[gssapi_data] + +[form_data] + +[provider_config] +openidc database url = postgresql://{{ ipsilon_db_user }}:{{ ipsilon_db_password }}@{{ ipsilon_db_host }}/{{ ipsilon_db_name }} +openidc static database url = postgresql://{{ ipsilon_db_user }}:{{ ipsilon_db_password }}@{{ ipsilon_db_host }}/{{ ipsilon_db_name }} +openidc enabled extensions = ipsilon, mbs, fedora-account +openidc endpoint url = https://{{ idp_hostname }}/idp/openidc/ +openidc documentation url = https://ipsilonproject.org/doc/openidc/ +openidc policy url = http://www.example.com/ +openidc tos url = http://www.example.com/ +openidc idp key file = /var/lib/ipsilon/idp/openidc/openidc.key +openidc idp sig key id = 1633884828-sig +openidc idp subject salt = {{ ipsilon_oidc_salt }} +openidc allow dynamic client registration = True +openidc default attribute mapping = [["*", "*"], ["_groups", "groups"], [["_extras", "cla"], "cla"], ["fullname", "name"], ["_username", "nickname"], ["_username", "preferred_username"], ["fasIRCNick", "ircnick"], ["fasLocale", "locale"], ["fasTimeZone", "zoneinfo"], ["fasTimeZone", "timezone"], ["fasWebsiteURL", "website"], ["fasGPGKeyId", "gpg_keyid"], ["ipaSshPubKey", "ssh_key"], ["fasIsPrivate", "privacy"], ["fullname", "human_name"]] +openidc default allowed attributes = ["*"] +openidc access token lifetime = 3600 +openidc refresh token lifetime = 31536000 +global enabled = openidc,saml2,openid +openid default allowed attributes = ["*"] +openid database url = postgresql://{{ ipsilon_db_user }}:{{ ipsilon_db_password }}@{{ ipsilon_db_host }}/{{ ipsilon_db_name }} +openid default email domain = +openid endpoint url = https://{{ idp_hostname }}/idp/openid/ +openid identity url template = https://{{ idp_hostname }}/idp/openid/id/%(username)s +openid trusted roots = {{ ipsilon_openid_trust_roots }} +openid untrusted roots = +openid enabled extensions = Attribute Exchange, CLAs, Fedora Teams, Simple Registration, Teams, API +openid default attribute mapping = [["*", "*"], ["_groups", "groups"], [["_extras", "cla"], "cla"], ["fullname", "name"], ["_username", "nickname"], ["_username", "preferred_username"], ["fasIRCNick", "ircnick"], ["fasLocale", "locale"], ["fasTimeZone", "zoneinfo"], ["fasTimeZone", "timezone"], ["fasWebsiteURL", "website"], ["fasGPGKeyId", "gpg_keyid"], ["ipaSshPubKey", "ssh_key"], ["fasIsPrivate", "privacy"], ["fullname", "human_name"], ["mail", "email"]] +saml2 idp storage path = /var/lib/ipsilon/idp/saml2 +saml2 idp metadata file = metadata.xml +saml2 idp certificate file = /var/lib/ipsilon/idp/saml2/idp.pem +saml2 idp key file = /var/lib/ipsilon/idp/saml2/idp.key +saml2 idp nameid salt = {{ ipsilon_saml2_salt }} +saml2 idp metadata validity = 1825 +saml2 session database url = postgresql://{{ ipsilon_db_user }}:{{ ipsilon_db_password }}@{{ ipsilon_db_host }}/{{ ipsilon_db_name }} + +[openidc_data] + +[saml2_data] + +[openid_data] + +[authz_config] +global enabled = allow + +[allow_data] + diff --git a/templates/ipsilon.conf b/templates/ipsilon.conf new file mode 100644 index 0000000..e2d4d68 --- /dev/null +++ b/templates/ipsilon.conf @@ -0,0 +1,24 @@ +[global] +debug = True +tools.log_request_response.on = True +template_dir = "templates" +cache_dir = "/var/cache/ipsilon" +cleanup_interval = 30 +db.conn.log = False +db.echo = False + +base.mount = "/idp" +base.dir = "/usr/share/ipsilon" +#admin.config.db = "configfile:///etc/ipsilon/idp/configuration.conf" +admin.config.db = "postgresql://{{ ipsilon_db_user }}:{{ ipsilon_db_password }}@{{ ipsilon_db_host }}/{{ ipsilon_db_name }}" +user.prefs.db = "postgresql://{{ ipsilon_db_user }}:{{ ipsilon_db_password }}@{{ ipsilon_db_host }}/{{ ipsilon_db_name }}" +transactions.db = "postgresql://{{ ipsilon_db_user }}:{{ ipsilon_db_password }}@{{ ipsilon_db_host }}/{{ ipsilon_db_name }}" + +tools.sessions.on = True +tools.sessions.name = "idp_ipsilon_session_id" +tools.sessions.storage_type = "file" +tools.sessions.storage_path = "/var/lib/ipsilon/idp/sessions" +tools.sessions.path = "/idp" +tools.sessions.timeout = 30 +tools.sessions.httponly = True +tools.sessions.secure = True diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..0d75ed2 --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,3 @@ +--- +# vars file - Nothing should really go here but dynamic imports +# and truely static items \ No newline at end of file