diff options
author | Hugo Valente <82235632+hugovalente-pm@users.noreply.github.com> | 2024-04-10 10:00:48 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-10 12:00:48 +0300 |
commit | d9c5f204588e196b90046ecd39cbd1e4cb35b69b (patch) | |
tree | 93148e25110667622a8e23204593907f020b304b | |
parent | f4ada267cbe2311083785a59f8fe9afdbb6911ba (diff) |
add Okta SSO integration (#17351)
* add Okta SSO integration
* add Okta SSO integration
* add new authentication integration logic to the generation script
* apply Ilya's edits
* Apply suggestions from code review
* Update integrations/cloud-authentication/metadata.yaml
* Update okta_sso.md
---------
Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>
Co-authored-by: Ilya Mashchenko <ilya@netdata.cloud>
-rw-r--r-- | integrations/categories.yaml | 8 | ||||
-rw-r--r-- | integrations/cloud-authentication/integrations/okta_sso.md | 49 | ||||
-rw-r--r-- | integrations/cloud-authentication/metadata.yaml | 41 | ||||
-rw-r--r-- | integrations/gen_docs_integrations.py | 69 | ||||
-rwxr-xr-x | integrations/gen_integrations.py | 110 | ||||
-rw-r--r-- | integrations/schemas/authentication.json | 71 | ||||
-rw-r--r-- | integrations/templates/overview.md | 2 | ||||
-rw-r--r-- | integrations/templates/overview/authentication.md | 9 |
8 files changed, 356 insertions, 3 deletions
diff --git a/integrations/categories.yaml b/integrations/categories.yaml index 670244340a..cd311858b1 100644 --- a/integrations/categories.yaml +++ b/integrations/categories.yaml @@ -418,7 +418,7 @@ name: exporters description: "Exporter Integrations" most_popular: true - priority: 5 + priority: 6 children: [] - id: notify name: notifications @@ -438,3 +438,9 @@ most_popular: true priority: 1 children: [] +- id: auth + name: authentication + description: "Authentication & Authorization" + most_popular: true + priority: 5 + children: [] diff --git a/integrations/cloud-authentication/integrations/okta_sso.md b/integrations/cloud-authentication/integrations/okta_sso.md new file mode 100644 index 0000000000..3f6dd15a0b --- /dev/null +++ b/integrations/cloud-authentication/integrations/okta_sso.md @@ -0,0 +1,49 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/integrations/cloud-authentication/integrations/okta_sso.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/integrations/cloud-authentication/metadata.yaml" +sidebar_label: "Okta SSO" +learn_status: "Published" +learn_rel_path: "Authentication" +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE AUTHENTICATION'S metadata.yaml FILE" +endmeta--> + +# Okta SSO + + +<img src="https://netdata.cloud/img/okta.png" width="150"/> + + +Integrate your organization's Okta account with Netdata to better manage your team's access controls to Netdata Cloud. + + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Setup + +### Prerequisites +- An Okta account +- A Netdata Cloud account +- Access to the Space as an administrator +- Space needs to be on Business plan or higher + +### Setting up Okta +Steps needed to be done on Okta Admin Portal: +1. Click on **Applications** tab and choose to **Browse App Catalogue** +2. Find Netdata's preconfigured app for easy setup and click **Add Integration** +3. Give the app the preferred **Application label**, for when it is displayed in your apps dashboard, +and click Next to move to the Sign-On options tab +5. In the **Sign-On Options** all the values we expect are already filled and no additional data is required +6. Click **Done**. You are be able to go back and edit any fields later if need be +7. Go to the **Assignments** tab and enter the People or Group assignments as per your organization’s policies + +### Netdata Configuration Steps +1. Click on the Space settings cog (located above your profile icon) +2. Click on the **Authentication** tab +3. On the Okta SSO card, click on **Configure** +4. Fill in the required credentials, you get them from **Okta Admin Portal**: + - **Issuer URL** you can get it from your profile icon on top, e.g. `https://company-name.okta.com` + - **Client ID** you can get it from **General** tab on application you configured on Okta + - **Client Secret** you can get it from **General** tab on application you configured on Okta + Note: [Okta - Find your application credentials](https://developer.okta.com/docs/guides/find-your-app-credentials/main/) + + diff --git a/integrations/cloud-authentication/metadata.yaml b/integrations/cloud-authentication/metadata.yaml new file mode 100644 index 0000000000..18925d5d53 --- /dev/null +++ b/integrations/cloud-authentication/metadata.yaml @@ -0,0 +1,41 @@ +# yamllint disable rule:line-length +--- +- id: 'okta-authentication' + meta: + name: 'Okta SSO' + link: 'https://netdata.cloud' + categories: + - auth + icon_filename: 'okta.png' + keywords: + - sso + - okta + - okta-sso + overview: + authentication_description: "Integrate your organization's Okta account with Netdata to better manage your team's access controls to Netdata Cloud." + authentication_limitations: '' + setup: + description: | + ### Prerequisites + - An Okta account + - A Netdata Cloud account + - Access to the Space as an administrator + - Space needs to be on the Business plan or higher + + ### Setting up Okta + Steps needed to be done on Okta Admin Portal: + 1. Click on **Applications** tab and choose to **Browse App Catalogue** + 2. Find Netdata's preconfigured app for easy setup and click **Add Integration** + 3. Give the app, that will be in your apps dashboard, the preferred **Application label** and click **Next** to move to the Sign-On options tab + 4. In the **Sign-On Options** all the values we expect are already filled and no additional data is required + 5. Click **Done**. You are able to go back and edit any fields later if need be + 6. Go to the **Assignments** tab and enter the People or Group assignments as per your organization’s policies + + ### Netdata Configuration Steps + 1. Click on the Space settings cog (located above your profile icon) + 2. Click on the **Authentication** tab + 3. On the Okta SSO card, click on **Configure** + 4. Fill in the [required credentials](https://developer.okta.com/docs/guides/find-your-app-credentials/main/), you get them from **Okta Admin Portal**: + - **Issuer URL** you can get it from your profile icon on top, e.g. `https://company-name.okta.com` + - **Client ID** you can get it from **General** tab on application you configured on Okta + - **Client Secret** you can get it from **General** tab on application you configured on Okta diff --git a/integrations/gen_docs_integrations.py b/integrations/gen_docs_integrations.py index d6306e811a..27d2ce72e3 100644 --- a/integrations/gen_docs_integrations.py +++ b/integrations/gen_docs_integrations.py @@ -25,6 +25,9 @@ def cleanup(): for element in Path("integrations/cloud-notifications").glob('**/*/'): if "integrations" in str(element) and not "metadata.yaml" in str(element): shutil.rmtree(element) + for element in Path("integrations/cloud-authentication").glob('**/*/'): + if "integrations" in str(element) and not "metadata.yaml" in str(element): + shutil.rmtree(element) def generate_category_from_name(category_fragment, category_array): """ @@ -80,6 +83,9 @@ def add_custom_edit_url(markdown_string, meta_yaml_link, sidebar_label_string, m elif mode == 'agent-notifications': path_to_md_file = meta_yaml_link.replace("metadata.yaml", "README") + elif mode == 'cloud-authentication': + path_to_md_file = meta_yaml_link.replace("metadata.yaml", f'integrations/{clean_string(sidebar_label_string)}') + output = markdown_string.replace( "<!--startmeta", f'<!--startmeta\ncustom_edit_url: \"{path_to_md_file}.md\"') @@ -244,6 +250,39 @@ endmeta--> except Exception as e: print("Exception in notification md construction", e, integration['id']) + + # AUTHENTICATIONS + elif mode == 'authentication': + if True: + # initiate the variables for the authentication method + meta_yaml = integration['edit_link'].replace("blob", "edit") + sidebar_label = integration['meta']['name'] + learn_rel_path = generate_category_from_name(integration['meta']['categories'][0].split("."), categories) + + # build the markdown string + md = \ + f"""<!--startmeta +meta_yaml: "{meta_yaml}" +sidebar_label: "{sidebar_label}" +learn_status: "Published" +learn_rel_path: "{learn_rel_path.replace("authentication", "Authentication")}" +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE AUTHENTICATION'S metadata.yaml FILE" +endmeta--> + +{create_overview(integration, integration['meta']['icon_filename'])}""" + + if integration['setup']: + md += f""" +{integration['setup']} +""" + + if integration['troubleshooting']: + md += f""" +{integration['troubleshooting']} +""" + + # except Exception as e: + # print("Exception in authentication md construction", e, integration['id']) if "community" in integration['meta'].keys(): community = "<img src=\"https://img.shields.io/badge/maintained%20by-Community-blue\" />" @@ -329,6 +368,29 @@ def write_to_file(path, md, meta_yaml, sidebar_label, community, mode='default') except FileNotFoundError as e: print("Exception in writing to file", e) + elif mode == 'authentication': + + name = clean_string(integration['meta']['name']) + + if not Path(f'{path}/integrations').exists(): + Path(f'{path}/integrations').mkdir() + + # proper_edit_name = meta_yaml.replace( + # "metadata.yaml", f'integrations/{clean_string(sidebar_label)}.md\"') + + md = add_custom_edit_url(md, meta_yaml, sidebar_label, mode='cloud-authentication') + + finalpath = f'{path}/integrations/{name}.md' + + try: + clean_and_write( + md, + Path(finalpath) + ) + + except FileNotFoundError as e: + print("Exception in writing to file", e) + def make_symlinks(symlink_dict): """ @@ -386,5 +448,12 @@ for integration in integrations: path = build_path(meta_yaml) write_to_file(path, md, meta_yaml, sidebar_label, community, mode='notification') + elif integration['integration_type'] == "authentication": + + meta_yaml, sidebar_label, learn_rel_path, md, community = build_readme_from_integration( + integration, mode='authentication') + path = build_path(meta_yaml) + write_to_file(path, md, meta_yaml, sidebar_label, community, mode='authentication') + make_symlinks(symlink_dict) diff --git a/integrations/gen_integrations.py b/integrations/gen_integrations.py index 2b188b630f..c217c976db 100755 --- a/integrations/gen_integrations.py +++ b/integrations/gen_integrations.py @@ -45,6 +45,10 @@ NOTIFICATION_SOURCES = [ (AGENT_REPO, INTEGRATIONS_PATH / 'cloud-notifications' / 'metadata.yaml', False), ] +AUTHENTICATION_SOURCES = [ + (AGENT_REPO, INTEGRATIONS_PATH / 'cloud-authentication' / 'metadata.yaml', False), +] + COLLECTOR_RENDER_KEYS = [ 'alerts', 'metrics', @@ -66,6 +70,12 @@ NOTIFICATION_RENDER_KEYS = [ 'troubleshooting', ] +AUTHENTICATION_RENDER_KEYS = [ + 'overview', + 'setup', + 'troubleshooting', +] + CUSTOM_TAG_PATTERN = re.compile('\\{% if .*?%\\}.*?\\{% /if %\\}|\\{%.*?%\\}', flags=re.DOTALL) FIXUP_BLANK_PATTERN = re.compile('\\\\\\n *\\n') @@ -117,6 +127,11 @@ NOTIFICATION_VALIDATOR = Draft7Validator( registry=registry, ) +AUTHENTICATION_VALIDATOR = Draft7Validator( + {'$ref': './authentication.json#'}, + registry=registry, +) + COLLECTOR_VALIDATOR = Draft7Validator( {'$ref': './collector.json#'}, registry=registry, @@ -384,6 +399,51 @@ def load_notifications(): return ret +def _load_authentication_file(file, repo): + debug(f'Loading { file }.') + data = load_yaml(file) + + if not data: + return [] + + try: + AUTHENTICATION_VALIDATOR.validate(data) + except ValidationError: + warn(f'Failed to validate { file } against the schema.', file) + return [] + + if 'id' in data: + data['integration_type'] = 'authentication' + data['_src_path'] = file + data['_repo'] = repo + data['_index'] = 0 + + return [data] + else: + ret = [] + + for idx, item in enumerate(data): + item['integration_type'] = 'authentication' + item['_src_path'] = file + item['_repo'] = repo + item['_index'] = idx + ret.append(item) + + return ret + + +def load_authentications(): + ret = [] + + for repo, path, match in AUTHENTICATION_SOURCES: + if match and path.exists() and path.is_dir(): + for file in path.glob(METADATA_PATTERN): + ret.extend(_load_authentication_file(file, repo)) + elif not match and path.exists() and path.is_file(): + ret.extend(_load_authentication_file(path, repo)) + + return ret + def make_id(meta): if 'monitored_instance' in meta: @@ -652,6 +712,49 @@ def render_notifications(categories, notifications, ids): return notifications, clean_notifications, ids +def render_authentications(categories, authentications, ids): + debug('Sorting authentications.') + + sort_integrations(authentications) + + debug('Checking authentication ids.') + + authentications, ids = dedupe_integrations(authentications, ids) + + clean_authentications = [] + + for item in authentications: + item['edit_link'] = make_edit_link(item) + + clean_item = deepcopy(item) + + for key in AUTHENTICATION_RENDER_KEYS: + + if key in item.keys(): + template = get_jinja_env().get_template(f'{ key }.md') + data = template.render(entry=item, clean=False) + clean_data = template.render(entry=item, clean=True) + + if 'variables' in item['meta']: + template = get_jinja_env().from_string(data) + data = template.render(variables=item['meta']['variables'], clean=False) + template = get_jinja_env().from_string(clean_data) + clean_data = template.render(variables=item['meta']['variables'], clean=True) + else: + data = '' + clean_data = '' + + item[key] = data + clean_item[key] = clean_data + + for k in ['_src_path', '_repo', '_index']: + del item[k], clean_item[k] + + clean_authentications.append(clean_item) + + return authentications, clean_authentications, ids + + def render_integrations(categories, integrations): template = get_jinja_env().get_template('integrations.js') data = template.render( @@ -675,16 +778,19 @@ def main(): deploy = load_deploy() exporters = load_exporters() notifications = load_notifications() + authentications = load_authentications() collectors, clean_collectors, ids = render_collectors(categories, collectors, dict()) deploy, clean_deploy, ids = render_deploy(distros, categories, deploy, ids) exporters, clean_exporters, ids = render_exporters(categories, exporters, ids) notifications, clean_notifications, ids = render_notifications(categories, notifications, ids) + authentications, clean_authentications, ids = render_authentications(categories, authentications, ids) + - integrations = collectors + deploy + exporters + notifications + integrations = collectors + deploy + exporters + notifications + authentications render_integrations(categories, integrations) - clean_integrations = clean_collectors + clean_deploy + clean_exporters + clean_notifications + clean_integrations = clean_collectors + clean_deploy + clean_exporters + clean_notifications + clean_authentications render_json(categories, clean_integrations) diff --git a/integrations/schemas/authentication.json b/integrations/schemas/authentication.json new file mode 100644 index 0000000000..bf302837a2 --- /dev/null +++ b/integrations/schemas/authentication.json @@ -0,0 +1,71 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Netdata authentication mechanism metadata.", + "oneOf": [ + { + "$ref": "#/$defs/entry" + }, + { + "type": "array", + "minLength": 1, + "items": { + "$ref": "#/$defs/entry" + } + } + ], + "$defs": { + "entry": { + "type": "object", + "description": "Data for a single authentication method.", + "properties": { + "id": { + "$ref": "./shared.json#/$defs/id" + }, + "meta": { + "$ref": "./shared.json#/$defs/instance" + }, + "keywords": { + "$ref": "./shared.json#/$defs/keywords" + }, + "overview": { + "type": "object", + "description": "General information about the authentication method.", + "properties": { + "authentication_description": { + "type": "string", + "description": "General description of what the authentication method does." + }, + "authentication_limitations": { + "type": "string", + "description": "Explanation of any limitations of the authentication method." + } + }, + "required": [ + "authentication_description", + "authentication_limitations" + ] + }, + "setup": { + "oneOf": [ + { + "$ref": "./shared.json#/$defs/short_setup" + }, + { + "$ref": "./shared.json#/$defs/full_setup" + } + ] + }, + "troubleshooting": { + "$ref": "./shared.json#/$defs/troubleshooting" + } + }, + "required": [ + "id", + "meta", + "keywords", + "overview", + "setup" + ] + } + } +} diff --git a/integrations/templates/overview.md b/integrations/templates/overview.md index b89e515439..3063b68606 100644 --- a/integrations/templates/overview.md +++ b/integrations/templates/overview.md @@ -4,4 +4,6 @@ [% include 'overview/exporter.md' %] [% elif entry.integration_type == 'notification' %] [% include 'overview/notification.md' %] +[% elif entry.integration_type == 'authentication' %] +[% include 'overview/authentication.md' %] [% endif %] diff --git a/integrations/templates/overview/authentication.md b/integrations/templates/overview/authentication.md new file mode 100644 index 0000000000..f7fa77520f --- /dev/null +++ b/integrations/templates/overview/authentication.md @@ -0,0 +1,9 @@ +# [[ entry.meta.name ]] + +[[ entry.overview.authentication_description ]] +[% if entry.overview.authentication_limitations %] + +## Limitations + +[[ entry.overview.authentication_limitations ]] +[% endif %] |