mirror of
https://github.com/netbirdio/ansible-netbird.git
synced 2026-05-22 18:43:36 -07:00
sync with v0.67.0
This commit is contained in:
@@ -20,8 +20,10 @@ This collection provides comprehensive management of NetBird resources:
|
||||
- **Routes** - Manage legacy routes (deprecated, use Networks instead)
|
||||
- **DNS** - Configure nameserver groups and DNS settings
|
||||
- **Posture Checks** - Define security compliance requirements
|
||||
- **Accounts** - Manage account-wide settings
|
||||
- **Accounts** - Manage account-wide settings (including extra settings, auto-update, peer expose)
|
||||
- **Tokens** - Create and manage personal access tokens
|
||||
- **Identity Providers** - Configure identity providers (Google, Okta, Entra, OIDC, etc.)
|
||||
- **Invites** - Manage user invite links with expiration and regeneration
|
||||
- **Info** - Gather information about any resource
|
||||
|
||||
## Requirements
|
||||
@@ -189,6 +191,8 @@ Manage NetBird access policies.
|
||||
name: "developers-to-servers"
|
||||
description: "Allow developers SSH access to servers"
|
||||
enabled: true
|
||||
source_posture_checks:
|
||||
- "posture-check-id"
|
||||
rules:
|
||||
- name: "ssh-access"
|
||||
sources:
|
||||
@@ -200,6 +204,16 @@ Manage NetBird access policies.
|
||||
ports:
|
||||
- "22"
|
||||
action: "accept"
|
||||
- name: "web-access"
|
||||
sources:
|
||||
- "developers-group-id"
|
||||
destinations:
|
||||
- "servers-group-id"
|
||||
protocol: "tcp"
|
||||
port_ranges:
|
||||
- start: 8000
|
||||
end: 9000
|
||||
action: "accept"
|
||||
state: present
|
||||
```
|
||||
|
||||
@@ -383,6 +397,11 @@ Manage NetBird account settings.
|
||||
jwt_groups_enabled: true
|
||||
jwt_groups_claim_name: "groups"
|
||||
dns_domain: "netbird.example.com"
|
||||
auto_update_always: true
|
||||
auto_update_version: "latest"
|
||||
peer_expose_enabled: true
|
||||
extra_peer_approval_enabled: false
|
||||
extra_network_traffic_logs_enabled: true
|
||||
state: present
|
||||
```
|
||||
|
||||
@@ -407,6 +426,72 @@ Manage NetBird personal access tokens.
|
||||
when: new_token.changed
|
||||
```
|
||||
|
||||
### netbird_idp
|
||||
|
||||
Manage NetBird identity providers.
|
||||
|
||||
```yaml
|
||||
# Create an OIDC identity provider
|
||||
- name: Configure identity provider
|
||||
community.ansible_netbird.netbird_idp:
|
||||
api_url: "{{ netbird_api_url }}"
|
||||
api_token: "{{ netbird_api_token }}"
|
||||
name: "corporate-sso"
|
||||
type: "oidc"
|
||||
issuer: "https://auth.example.com"
|
||||
client_id: "your-client-id"
|
||||
client_secret: "your-client-secret"
|
||||
state: present
|
||||
|
||||
# Create a Google identity provider
|
||||
- name: Configure Google IDP
|
||||
community.ansible_netbird.netbird_idp:
|
||||
api_url: "{{ netbird_api_url }}"
|
||||
api_token: "{{ netbird_api_token }}"
|
||||
name: "google-workspace"
|
||||
type: "google"
|
||||
issuer: "https://accounts.google.com"
|
||||
client_id: "your-google-client-id"
|
||||
client_secret: "your-google-client-secret"
|
||||
state: present
|
||||
```
|
||||
|
||||
### netbird_invite
|
||||
|
||||
Manage NetBird user invites.
|
||||
|
||||
```yaml
|
||||
# Create a user invite
|
||||
- name: Invite a new user
|
||||
community.ansible_netbird.netbird_invite:
|
||||
api_url: "{{ netbird_api_url }}"
|
||||
api_token: "{{ netbird_api_token }}"
|
||||
email: "newuser@example.com"
|
||||
name: "New User"
|
||||
role: "user"
|
||||
auto_groups:
|
||||
- "developers-group-id"
|
||||
expires_in: 604800 # 7 days
|
||||
state: present
|
||||
|
||||
# Regenerate an expired invite
|
||||
- name: Regenerate invite link
|
||||
community.ansible_netbird.netbird_invite:
|
||||
api_url: "{{ netbird_api_url }}"
|
||||
api_token: "{{ netbird_api_token }}"
|
||||
email: "newuser@example.com"
|
||||
regenerate: true
|
||||
state: present
|
||||
|
||||
# Delete an invite
|
||||
- name: Remove invite
|
||||
community.ansible_netbird.netbird_invite:
|
||||
api_url: "{{ netbird_api_url }}"
|
||||
api_token: "{{ netbird_api_token }}"
|
||||
invite_id: "invite-id-123"
|
||||
state: absent
|
||||
```
|
||||
|
||||
### netbird_info
|
||||
|
||||
Gather information about NetBird resources.
|
||||
@@ -434,7 +519,7 @@ Gather information about NetBird resources.
|
||||
register: me
|
||||
```
|
||||
|
||||
Available resources: `accounts`, `users`, `peers`, `groups`, `setup_keys`, `policies`, `networks`, `routes`, `dns_nameservers`, `dns_settings`, `posture_checks`, `events`, `countries`, `current_user`
|
||||
Available resources: `accounts`, `users`, `peers`, `groups`, `setup_keys`, `policies`, `networks`, `routes`, `dns_nameservers`, `dns_settings`, `posture_checks`, `events`, `countries`, `current_user`, `identity_providers`, `invites`
|
||||
|
||||
## Role Usage
|
||||
|
||||
@@ -516,6 +601,7 @@ This collection implements the [NetBird REST API](https://docs.netbird.io/api).
|
||||
- [Networks](https://docs.netbird.io/api/resources/networks)
|
||||
- [DNS](https://docs.netbird.io/api/resources/dns)
|
||||
- [Posture Checks](https://docs.netbird.io/api/resources/posture-checks)
|
||||
- [Identity Providers](https://docs.netbird.io/api/resources/identity-providers)
|
||||
- [Events](https://docs.netbird.io/api/resources/events)
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -158,6 +158,28 @@ netbird_posture_checks: []
|
||||
# min_version: "0.25.0"
|
||||
# state: present
|
||||
|
||||
# Identity provider management
|
||||
netbird_identity_providers: []
|
||||
# Example:
|
||||
# netbird_identity_providers:
|
||||
# - name: "google-idp"
|
||||
# type: "google" # entra, google, microsoft, oidc, okta, pocketid, zitadel
|
||||
# issuer: "https://accounts.google.com"
|
||||
# client_id: "your-client-id"
|
||||
# client_secret: "your-client-secret"
|
||||
# state: present
|
||||
|
||||
# User invite management
|
||||
netbird_invites: []
|
||||
# Example:
|
||||
# netbird_invites:
|
||||
# - email: "newuser@example.com"
|
||||
# name: "New User"
|
||||
# role: "user" # admin, user
|
||||
# auto_groups: []
|
||||
# expires_in: 604800 # 7 days in seconds
|
||||
# state: present
|
||||
|
||||
# Account settings
|
||||
netbird_account_settings: {}
|
||||
# Example:
|
||||
|
||||
@@ -244,8 +244,8 @@ class NetBirdAPI:
|
||||
"""Get a specific peer."""
|
||||
return self.get(f'/api/peers/{peer_id}')
|
||||
|
||||
def update_peer(self, peer_id, name=None, ssh_enabled=None, login_expiration_enabled=None,
|
||||
inactivity_expiration_enabled=None, approval_required=None):
|
||||
def update_peer(self, peer_id, name=None, ssh_enabled=None, login_expiration_enabled=None,
|
||||
inactivity_expiration_enabled=None, approval_required=None, ip=None):
|
||||
"""Update a peer."""
|
||||
data = {}
|
||||
if name is not None:
|
||||
@@ -258,6 +258,8 @@ class NetBirdAPI:
|
||||
data['inactivity_expiration_enabled'] = inactivity_expiration_enabled
|
||||
if approval_required is not None:
|
||||
data['approval_required'] = approval_required
|
||||
if ip is not None:
|
||||
data['ip'] = ip
|
||||
return self.put(f'/api/peers/{peer_id}', data=data)
|
||||
|
||||
def delete_peer(self, peer_id):
|
||||
@@ -288,11 +290,9 @@ class NetBirdAPI:
|
||||
}
|
||||
return self.post('/api/setup-keys', data=data)
|
||||
|
||||
def update_setup_key(self, key_id, name=None, revoked=None, auto_groups=None):
|
||||
"""Update a setup key."""
|
||||
def update_setup_key(self, key_id, revoked=None, auto_groups=None):
|
||||
"""Update a setup key. Only revoked and auto_groups can be changed after creation."""
|
||||
data = {}
|
||||
if name is not None:
|
||||
data['name'] = name
|
||||
if revoked is not None:
|
||||
data['revoked'] = revoked
|
||||
if auto_groups is not None:
|
||||
@@ -346,7 +346,7 @@ class NetBirdAPI:
|
||||
"""Get a specific policy."""
|
||||
return self.get(f'/api/policies/{policy_id}')
|
||||
|
||||
def create_policy(self, name, enabled=True, description='', rules=None):
|
||||
def create_policy(self, name, enabled=True, description='', rules=None, source_posture_checks=None):
|
||||
"""Create a new policy."""
|
||||
data = {
|
||||
'name': name,
|
||||
@@ -354,9 +354,12 @@ class NetBirdAPI:
|
||||
'description': description,
|
||||
'rules': rules or []
|
||||
}
|
||||
if source_posture_checks is not None:
|
||||
data['source_posture_checks'] = source_posture_checks
|
||||
return self.post('/api/policies', data=data)
|
||||
|
||||
def update_policy(self, policy_id, name=None, enabled=None, description=None, rules=None):
|
||||
def update_policy(self, policy_id, name=None, enabled=None, description=None, rules=None,
|
||||
source_posture_checks=None):
|
||||
"""Update a policy."""
|
||||
data = {}
|
||||
if name is not None:
|
||||
@@ -367,6 +370,8 @@ class NetBirdAPI:
|
||||
data['description'] = description
|
||||
if rules is not None:
|
||||
data['rules'] = rules
|
||||
if source_posture_checks is not None:
|
||||
data['source_posture_checks'] = source_posture_checks
|
||||
return self.put(f'/api/policies/{policy_id}', data=data)
|
||||
|
||||
def delete_policy(self, policy_id):
|
||||
@@ -412,11 +417,13 @@ class NetBirdAPI:
|
||||
"""Get a specific network router."""
|
||||
return self.get(f'/api/networks/{network_id}/routers/{router_id}')
|
||||
|
||||
def create_network_router(self, network_id, peer_id=None, peer_groups=None, metric=9999, masquerade=False):
|
||||
def create_network_router(self, network_id, peer_id=None, peer_groups=None, metric=9999,
|
||||
masquerade=False, enabled=True):
|
||||
"""Create a new network router."""
|
||||
data = {
|
||||
'metric': metric,
|
||||
'masquerade': masquerade
|
||||
'masquerade': masquerade,
|
||||
'enabled': enabled
|
||||
}
|
||||
if peer_id:
|
||||
data['peer'] = peer_id
|
||||
@@ -424,8 +431,8 @@ class NetBirdAPI:
|
||||
data['peer_groups'] = peer_groups
|
||||
return self.post(f'/api/networks/{network_id}/routers', data=data)
|
||||
|
||||
def update_network_router(self, network_id, router_id, peer_id=None, peer_groups=None,
|
||||
metric=None, masquerade=None):
|
||||
def update_network_router(self, network_id, router_id, peer_id=None, peer_groups=None,
|
||||
metric=None, masquerade=None, enabled=None):
|
||||
"""Update a network router."""
|
||||
data = {}
|
||||
if peer_id is not None:
|
||||
@@ -436,6 +443,8 @@ class NetBirdAPI:
|
||||
data['metric'] = metric
|
||||
if masquerade is not None:
|
||||
data['masquerade'] = masquerade
|
||||
if enabled is not None:
|
||||
data['enabled'] = enabled
|
||||
return self.put(f'/api/networks/{network_id}/routers/{router_id}', data=data)
|
||||
|
||||
def delete_network_router(self, network_id, router_id):
|
||||
@@ -647,6 +656,84 @@ class NetBirdAPI:
|
||||
"""List all events."""
|
||||
return self.get('/api/events')
|
||||
|
||||
# Identity Provider operations
|
||||
def list_identity_providers(self):
|
||||
"""List all identity providers."""
|
||||
return self.get('/api/identity-providers')
|
||||
|
||||
def get_identity_provider(self, idp_id):
|
||||
"""Get a specific identity provider."""
|
||||
return self.get(f'/api/identity-providers/{idp_id}')
|
||||
|
||||
def create_identity_provider(self, name, idp_type, issuer, client_id, client_secret):
|
||||
"""Create a new identity provider."""
|
||||
data = {
|
||||
'name': name,
|
||||
'type': idp_type,
|
||||
'issuer': issuer,
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret
|
||||
}
|
||||
return self.post('/api/identity-providers', data=data)
|
||||
|
||||
def update_identity_provider(self, idp_id, name=None, idp_type=None, issuer=None,
|
||||
client_id=None, client_secret=None):
|
||||
"""Update an identity provider."""
|
||||
data = {}
|
||||
if name is not None:
|
||||
data['name'] = name
|
||||
if idp_type is not None:
|
||||
data['type'] = idp_type
|
||||
if issuer is not None:
|
||||
data['issuer'] = issuer
|
||||
if client_id is not None:
|
||||
data['client_id'] = client_id
|
||||
if client_secret is not None:
|
||||
data['client_secret'] = client_secret
|
||||
return self.put(f'/api/identity-providers/{idp_id}', data=data)
|
||||
|
||||
def delete_identity_provider(self, idp_id):
|
||||
"""Delete an identity provider."""
|
||||
return self.delete(f'/api/identity-providers/{idp_id}')
|
||||
|
||||
# User Invite operations
|
||||
def list_user_invites(self):
|
||||
"""List all user invites."""
|
||||
return self.get('/api/users/invites')
|
||||
|
||||
def create_user_invite(self, email, name=None, role=None, auto_groups=None, expires_in=None):
|
||||
"""Create a new user invite."""
|
||||
data = {'email': email}
|
||||
if name is not None:
|
||||
data['name'] = name
|
||||
if role is not None:
|
||||
data['role'] = role
|
||||
if auto_groups is not None:
|
||||
data['auto_groups'] = auto_groups
|
||||
if expires_in is not None:
|
||||
data['expires_in'] = expires_in
|
||||
return self.post('/api/users/invites', data=data)
|
||||
|
||||
def delete_user_invite(self, invite_id):
|
||||
"""Delete a user invite."""
|
||||
return self.delete(f'/api/users/invites/{invite_id}')
|
||||
|
||||
def regenerate_user_invite(self, invite_id, expires_in=None):
|
||||
"""Regenerate a user invite token."""
|
||||
data = {}
|
||||
if expires_in is not None:
|
||||
data['expires_in'] = expires_in
|
||||
return self.post(f'/api/users/invites/{invite_id}/regenerate', data=data)
|
||||
|
||||
# User approval operations
|
||||
def approve_user(self, user_id):
|
||||
"""Approve a pending user."""
|
||||
return self.post(f'/api/users/{user_id}/approve')
|
||||
|
||||
def reject_user(self, user_id):
|
||||
"""Reject a pending user."""
|
||||
return self.delete(f'/api/users/{user_id}/reject')
|
||||
|
||||
# Geo-location operations
|
||||
def list_countries(self):
|
||||
"""List all countries."""
|
||||
|
||||
@@ -83,6 +83,53 @@ options:
|
||||
description:
|
||||
- Enable or disable experimental lazy connection.
|
||||
type: bool
|
||||
extra_peer_approval_enabled:
|
||||
description:
|
||||
- Enable or disable peer approval globally.
|
||||
- When enabled, all peers added will be in pending state until approved by an admin.
|
||||
type: bool
|
||||
extra_user_approval_required:
|
||||
description:
|
||||
- Enable manual approval for new users joining via domain matching.
|
||||
- When enabled, users are blocked with pending approval status until approved by an admin.
|
||||
type: bool
|
||||
extra_network_traffic_logs_enabled:
|
||||
description:
|
||||
- Enable or disable network traffic logging.
|
||||
- When enabled, all network traffic events from peers will be stored.
|
||||
type: bool
|
||||
extra_network_traffic_logs_groups:
|
||||
description:
|
||||
- Limits traffic logging to these groups.
|
||||
- If empty, all peers are enabled.
|
||||
type: list
|
||||
elements: str
|
||||
extra_network_traffic_packet_counter_enabled:
|
||||
description:
|
||||
- Enable or disable network traffic packet counter.
|
||||
- When enabled, network packets and their size will be counted and reported.
|
||||
type: bool
|
||||
auto_update_always:
|
||||
description:
|
||||
- When true, updates are installed automatically in the background.
|
||||
- When false, updates require user interaction from the UI.
|
||||
type: bool
|
||||
auto_update_version:
|
||||
description:
|
||||
- Set clients auto-update version.
|
||||
- Use "latest", "disabled", or a specific version (e.g., "0.50.1").
|
||||
type: str
|
||||
peer_expose_enabled:
|
||||
description:
|
||||
- Enable or disable peer expose.
|
||||
- When enabled, peers can expose local services through the reverse proxy using the CLI.
|
||||
type: bool
|
||||
peer_expose_groups:
|
||||
description:
|
||||
- Limits which peer groups are allowed to expose services.
|
||||
- If empty, all peers are allowed when peer expose is enabled.
|
||||
type: list
|
||||
elements: str
|
||||
extends_documentation_fragment:
|
||||
- community.ansible_netbird.netbird
|
||||
requirements:
|
||||
@@ -215,14 +262,44 @@ def build_settings_update(module):
|
||||
value = module.params.get(param)
|
||||
if value is not None:
|
||||
settings[api_field] = value
|
||||
|
||||
|
||||
# Build nested extra settings
|
||||
extra = {}
|
||||
if module.params.get('extra_peer_approval_enabled') is not None:
|
||||
extra['peer_approval_enabled'] = module.params['extra_peer_approval_enabled']
|
||||
if module.params.get('extra_user_approval_required') is not None:
|
||||
extra['user_approval_required'] = module.params['extra_user_approval_required']
|
||||
if module.params.get('extra_network_traffic_logs_enabled') is not None:
|
||||
extra['network_traffic_logs_enabled'] = module.params['extra_network_traffic_logs_enabled']
|
||||
if module.params.get('extra_network_traffic_logs_groups') is not None:
|
||||
extra['network_traffic_logs_groups'] = module.params['extra_network_traffic_logs_groups']
|
||||
if module.params.get('extra_network_traffic_packet_counter_enabled') is not None:
|
||||
extra['network_traffic_packet_counter_enabled'] = module.params['extra_network_traffic_packet_counter_enabled']
|
||||
if extra:
|
||||
settings['extra'] = extra
|
||||
|
||||
if module.params.get('auto_update_always') is not None:
|
||||
settings['auto_update_always'] = module.params['auto_update_always']
|
||||
if module.params.get('auto_update_version') is not None:
|
||||
settings['auto_update_version'] = module.params['auto_update_version']
|
||||
if module.params.get('peer_expose_enabled') is not None:
|
||||
settings['peer_expose_enabled'] = module.params['peer_expose_enabled']
|
||||
if module.params.get('peer_expose_groups') is not None:
|
||||
settings['peer_expose_groups'] = module.params['peer_expose_groups']
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def settings_need_update(current_settings, desired_settings):
|
||||
"""Check if account settings need to be updated."""
|
||||
for key, value in desired_settings.items():
|
||||
if current_settings.get(key) != value:
|
||||
if key == 'extra':
|
||||
# Compare nested extra settings
|
||||
current_extra = current_settings.get('extra', {})
|
||||
for extra_key, extra_value in value.items():
|
||||
if current_extra.get(extra_key) != extra_value:
|
||||
return True
|
||||
elif current_settings.get(key) != value:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -245,7 +322,16 @@ def run_module():
|
||||
routing_peer_dns_resolution_enabled=dict(type='bool'),
|
||||
dns_domain=dict(type='str'),
|
||||
network_range=dict(type='str'),
|
||||
lazy_connection_enabled=dict(type='bool')
|
||||
lazy_connection_enabled=dict(type='bool'),
|
||||
extra_peer_approval_enabled=dict(type='bool'),
|
||||
extra_user_approval_required=dict(type='bool'),
|
||||
extra_network_traffic_logs_enabled=dict(type='bool'),
|
||||
extra_network_traffic_logs_groups=dict(type='list', elements='str'),
|
||||
extra_network_traffic_packet_counter_enabled=dict(type='bool'),
|
||||
auto_update_always=dict(type='bool'),
|
||||
auto_update_version=dict(type='str'),
|
||||
peer_expose_enabled=dict(type='bool'),
|
||||
peer_expose_groups=dict(type='list', elements='str')
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2024, Community
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
"""Ansible module for managing NetBird identity providers."""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: netbird_idp
|
||||
short_description: Manage NetBird identity providers
|
||||
description:
|
||||
- Create, update, and delete identity providers in NetBird.
|
||||
- Identity providers are used to authenticate users via OIDC-compatible providers.
|
||||
version_added: "1.0.0"
|
||||
author:
|
||||
- Community
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- The desired state of the identity provider.
|
||||
type: str
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
idp_id:
|
||||
description:
|
||||
- The unique identifier of the identity provider.
|
||||
- Used for update or delete by ID.
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Name of the identity provider.
|
||||
- Required when state is present.
|
||||
- Used for lookup and creation.
|
||||
type: str
|
||||
type:
|
||||
description:
|
||||
- The type of identity provider.
|
||||
- Required when creating a new identity provider.
|
||||
type: str
|
||||
choices: ['entra', 'google', 'microsoft', 'oidc', 'okta', 'pocketid', 'zitadel']
|
||||
issuer:
|
||||
description:
|
||||
- The OIDC issuer URL for the identity provider.
|
||||
- Required when creating a new identity provider.
|
||||
type: str
|
||||
client_id:
|
||||
description:
|
||||
- The OIDC client ID for the identity provider.
|
||||
- Required when creating a new identity provider.
|
||||
type: str
|
||||
client_secret:
|
||||
description:
|
||||
- The OIDC client secret for the identity provider.
|
||||
- Required when creating a new identity provider.
|
||||
- This value is write-only and never returned by the API.
|
||||
type: str
|
||||
no_log: true
|
||||
extends_documentation_fragment:
|
||||
- community.ansible_netbird.netbird
|
||||
requirements:
|
||||
- python >= 3.6
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create an identity provider
|
||||
community.ansible_netbird.netbird_idp:
|
||||
api_url: "https://netbird.example.com"
|
||||
api_token: "{{ netbird_token }}"
|
||||
name: "corporate-okta"
|
||||
type: "okta"
|
||||
issuer: "https://dev-123456.okta.com"
|
||||
client_id: "0oa1b2c3d4e5f6g7h8i9"
|
||||
client_secret: "{{ okta_client_secret }}"
|
||||
state: present
|
||||
|
||||
- name: Update an identity provider by name
|
||||
community.ansible_netbird.netbird_idp:
|
||||
api_url: "https://netbird.example.com"
|
||||
api_token: "{{ netbird_token }}"
|
||||
name: "corporate-okta"
|
||||
type: "okta"
|
||||
issuer: "https://dev-789012.okta.com"
|
||||
client_id: "0oa1b2c3d4e5f6g7h8i9"
|
||||
client_secret: "{{ okta_client_secret }}"
|
||||
state: present
|
||||
|
||||
- name: Delete an identity provider by ID
|
||||
community.ansible_netbird.netbird_idp:
|
||||
api_url: "https://netbird.example.com"
|
||||
api_token: "{{ netbird_token }}"
|
||||
idp_id: "idp-id-123"
|
||||
state: absent
|
||||
|
||||
- name: Delete an identity provider by name
|
||||
community.ansible_netbird.netbird_idp:
|
||||
api_url: "https://netbird.example.com"
|
||||
api_token: "{{ netbird_token }}"
|
||||
name: "corporate-okta"
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
identity_provider:
|
||||
description: The identity provider object.
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
id:
|
||||
description: Identity provider ID.
|
||||
type: str
|
||||
name:
|
||||
description: Identity provider name.
|
||||
type: str
|
||||
type:
|
||||
description: Identity provider type.
|
||||
type: str
|
||||
issuer:
|
||||
description: OIDC issuer URL.
|
||||
type: str
|
||||
client_id:
|
||||
description: OIDC client ID.
|
||||
type: str
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.ansible_netbird.plugins.module_utils.netbird_api import (
|
||||
NetBirdAPI,
|
||||
NetBirdAPIError,
|
||||
netbird_argument_spec
|
||||
)
|
||||
|
||||
|
||||
def find_idp_by_name(api, name):
|
||||
"""Find an identity provider by name."""
|
||||
idps, _ = api.list_identity_providers()
|
||||
for idp in idps:
|
||||
if idp.get('name') == name:
|
||||
return idp
|
||||
return None
|
||||
|
||||
|
||||
def idp_needs_update(current, desired):
|
||||
"""Check if identity provider needs to be updated."""
|
||||
if 'name' in desired and desired['name'] is not None:
|
||||
if current.get('name') != desired['name']:
|
||||
return True
|
||||
|
||||
if 'type' in desired and desired['type'] is not None:
|
||||
if current.get('type') != desired['type']:
|
||||
return True
|
||||
|
||||
if 'issuer' in desired and desired['issuer'] is not None:
|
||||
if current.get('issuer') != desired['issuer']:
|
||||
return True
|
||||
|
||||
if 'client_id' in desired and desired['client_id'] is not None:
|
||||
if current.get('client_id') != desired['client_id']:
|
||||
return True
|
||||
|
||||
# Skip client_secret comparison since it's write-only/never returned by the API
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def run_module():
|
||||
"""Main module execution."""
|
||||
argument_spec = netbird_argument_spec()
|
||||
argument_spec.update(
|
||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
idp_id=dict(type='str'),
|
||||
name=dict(type='str'),
|
||||
type=dict(type='str', choices=['entra', 'google', 'microsoft', 'oidc', 'okta', 'pocketid', 'zitadel']),
|
||||
issuer=dict(type='str'),
|
||||
client_id=dict(type='str'),
|
||||
client_secret=dict(type='str', no_log=True)
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
('state', 'present', ['name'], True),
|
||||
],
|
||||
required_one_of=[
|
||||
('idp_id', 'name'),
|
||||
]
|
||||
)
|
||||
|
||||
api = NetBirdAPI(
|
||||
module,
|
||||
module.params['api_url'],
|
||||
module.params['api_token'],
|
||||
module.params['validate_certs']
|
||||
)
|
||||
|
||||
state = module.params['state']
|
||||
idp_id = module.params['idp_id']
|
||||
name = module.params['name']
|
||||
idp_type = module.params['type']
|
||||
issuer = module.params['issuer']
|
||||
client_id = module.params['client_id']
|
||||
client_secret = module.params['client_secret']
|
||||
|
||||
result = dict(
|
||||
changed=False,
|
||||
identity_provider={}
|
||||
)
|
||||
|
||||
try:
|
||||
# Find existing identity provider
|
||||
existing_idp = None
|
||||
if idp_id:
|
||||
try:
|
||||
existing_idp, _ = api.get_identity_provider(idp_id)
|
||||
except NetBirdAPIError as e:
|
||||
if e.status_code != 404:
|
||||
raise
|
||||
elif name:
|
||||
existing_idp = find_idp_by_name(api, name)
|
||||
|
||||
if state == 'absent':
|
||||
if existing_idp:
|
||||
if not module.check_mode:
|
||||
api.delete_identity_provider(existing_idp['id'])
|
||||
result['changed'] = True
|
||||
result['msg'] = 'Identity provider deleted successfully'
|
||||
module.exit_json(**result)
|
||||
|
||||
# state == 'present'
|
||||
if existing_idp:
|
||||
# Check if update is needed
|
||||
desired = {
|
||||
'name': name,
|
||||
'type': idp_type,
|
||||
'issuer': issuer,
|
||||
'client_id': client_id
|
||||
}
|
||||
|
||||
if idp_needs_update(existing_idp, desired):
|
||||
if not module.check_mode:
|
||||
idp, _ = api.update_identity_provider(
|
||||
existing_idp['id'],
|
||||
name=name,
|
||||
idp_type=idp_type,
|
||||
issuer=issuer,
|
||||
client_id=client_id,
|
||||
client_secret=client_secret
|
||||
)
|
||||
result['identity_provider'] = idp
|
||||
else:
|
||||
result['identity_provider'] = existing_idp
|
||||
result['changed'] = True
|
||||
else:
|
||||
result['identity_provider'] = existing_idp
|
||||
else:
|
||||
# Create new identity provider
|
||||
if not name:
|
||||
module.fail_json(msg="name is required when creating a new identity provider")
|
||||
|
||||
if not module.check_mode:
|
||||
idp, _ = api.create_identity_provider(
|
||||
name=name,
|
||||
idp_type=idp_type,
|
||||
issuer=issuer,
|
||||
client_id=client_id,
|
||||
client_secret=client_secret
|
||||
)
|
||||
result['identity_provider'] = idp
|
||||
result['changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
except NetBirdAPIError as e:
|
||||
module.fail_json(msg=str(e), status_code=e.status_code, response=e.response)
|
||||
|
||||
|
||||
def main():
|
||||
run_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -23,9 +23,10 @@ options:
|
||||
description:
|
||||
- Type of resource to gather information about.
|
||||
type: str
|
||||
choices: ['accounts', 'users', 'peers', 'groups', 'setup_keys', 'policies',
|
||||
'networks', 'routes', 'dns_nameservers', 'dns_settings',
|
||||
'posture_checks', 'events', 'countries', 'current_user']
|
||||
choices: ['accounts', 'users', 'peers', 'groups', 'setup_keys', 'policies',
|
||||
'networks', 'routes', 'dns_nameservers', 'dns_settings',
|
||||
'posture_checks', 'events', 'countries', 'current_user',
|
||||
'identity_providers', 'invites']
|
||||
required: true
|
||||
service_user:
|
||||
description:
|
||||
@@ -133,10 +134,10 @@ def run_module():
|
||||
resource=dict(
|
||||
type='str',
|
||||
required=True,
|
||||
choices=['accounts', 'users', 'peers', 'groups', 'setup_keys',
|
||||
'policies', 'networks', 'routes', 'dns_nameservers',
|
||||
choices=['accounts', 'users', 'peers', 'groups', 'setup_keys',
|
||||
'policies', 'networks', 'routes', 'dns_nameservers',
|
||||
'dns_settings', 'posture_checks', 'events', 'countries',
|
||||
'current_user']
|
||||
'current_user', 'identity_providers', 'invites']
|
||||
),
|
||||
service_user=dict(type='bool'),
|
||||
country_code=dict(type='str')
|
||||
@@ -192,6 +193,10 @@ def run_module():
|
||||
data, _ = api.list_events()
|
||||
elif resource == 'countries':
|
||||
data, _ = api.list_countries()
|
||||
elif resource == 'identity_providers':
|
||||
data, _ = api.list_identity_providers()
|
||||
elif resource == 'invites':
|
||||
data, _ = api.list_user_invites()
|
||||
else:
|
||||
module.fail_json(msg=f"Unknown resource type: {resource}")
|
||||
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2024, Community
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
"""Ansible module for managing NetBird user invites."""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: netbird_invite
|
||||
short_description: Manage NetBird user invites
|
||||
description:
|
||||
- Create, delete, and regenerate user invites in NetBird.
|
||||
- Invites are used to onboard new users to a NetBird account.
|
||||
- Invites cannot be updated, only created, deleted, or regenerated.
|
||||
version_added: "1.0.0"
|
||||
author:
|
||||
- Community
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- The desired state of the user invite.
|
||||
type: str
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
invite_id:
|
||||
description:
|
||||
- The unique identifier of the user invite.
|
||||
- Can be used to delete a specific invite when state is absent.
|
||||
type: str
|
||||
email:
|
||||
description:
|
||||
- Email address for the invite.
|
||||
- Required when state is present.
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Name of the invited user.
|
||||
type: str
|
||||
role:
|
||||
description:
|
||||
- Role to assign to the invited user.
|
||||
type: str
|
||||
choices: ['admin', 'user']
|
||||
default: user
|
||||
auto_groups:
|
||||
description:
|
||||
- List of group IDs to auto-assign to the invited user.
|
||||
type: list
|
||||
elements: str
|
||||
default: []
|
||||
expires_in:
|
||||
description:
|
||||
- Expiration time for the invite in seconds.
|
||||
type: int
|
||||
regenerate:
|
||||
description:
|
||||
- If true and an invite already exists for the email, regenerate the invite token.
|
||||
type: bool
|
||||
default: false
|
||||
extends_documentation_fragment:
|
||||
- community.ansible_netbird.netbird
|
||||
requirements:
|
||||
- python >= 3.6
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create a user invite
|
||||
community.ansible_netbird.netbird_invite:
|
||||
api_url: "https://netbird.example.com"
|
||||
api_token: "{{ netbird_token }}"
|
||||
email: "newuser@example.com"
|
||||
name: "New User"
|
||||
role: "user"
|
||||
auto_groups:
|
||||
- "group-id-1"
|
||||
expires_in: 604800
|
||||
state: present
|
||||
|
||||
- name: Create an admin invite
|
||||
community.ansible_netbird.netbird_invite:
|
||||
api_url: "https://netbird.example.com"
|
||||
api_token: "{{ netbird_token }}"
|
||||
email: "admin@example.com"
|
||||
name: "Admin User"
|
||||
role: "admin"
|
||||
state: present
|
||||
|
||||
- name: Regenerate an existing invite
|
||||
community.ansible_netbird.netbird_invite:
|
||||
api_url: "https://netbird.example.com"
|
||||
api_token: "{{ netbird_token }}"
|
||||
email: "newuser@example.com"
|
||||
regenerate: true
|
||||
state: present
|
||||
|
||||
- name: Delete an invite by email
|
||||
community.ansible_netbird.netbird_invite:
|
||||
api_url: "https://netbird.example.com"
|
||||
api_token: "{{ netbird_token }}"
|
||||
email: "newuser@example.com"
|
||||
state: absent
|
||||
|
||||
- name: Delete an invite by ID
|
||||
community.ansible_netbird.netbird_invite:
|
||||
api_url: "https://netbird.example.com"
|
||||
api_token: "{{ netbird_token }}"
|
||||
invite_id: "invite-id-123"
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
invite:
|
||||
description: The user invite object.
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
id:
|
||||
description: Invite ID.
|
||||
type: str
|
||||
email:
|
||||
description: Invited user email.
|
||||
type: str
|
||||
name:
|
||||
description: Invited user name.
|
||||
type: str
|
||||
role:
|
||||
description: Assigned role.
|
||||
type: str
|
||||
auto_groups:
|
||||
description: Auto-assigned group IDs.
|
||||
type: list
|
||||
expires_at:
|
||||
description: Invite expiration timestamp.
|
||||
type: str
|
||||
created_at:
|
||||
description: Invite creation timestamp.
|
||||
type: str
|
||||
expired:
|
||||
description: Whether the invite has expired.
|
||||
type: bool
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.ansible_netbird.plugins.module_utils.netbird_api import (
|
||||
NetBirdAPI,
|
||||
NetBirdAPIError,
|
||||
netbird_argument_spec
|
||||
)
|
||||
|
||||
|
||||
def find_invite_by_email(api, email):
|
||||
"""Find a user invite by email address."""
|
||||
invites, _ = api.list_user_invites()
|
||||
for invite in invites:
|
||||
if invite.get('email') == email:
|
||||
return invite
|
||||
return None
|
||||
|
||||
|
||||
def run_module():
|
||||
"""Main module execution."""
|
||||
argument_spec = netbird_argument_spec()
|
||||
argument_spec.update(
|
||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
invite_id=dict(type='str'),
|
||||
email=dict(type='str'),
|
||||
name=dict(type='str'),
|
||||
role=dict(type='str', choices=['admin', 'user'], default='user'),
|
||||
auto_groups=dict(type='list', elements='str', default=[]),
|
||||
expires_in=dict(type='int'),
|
||||
regenerate=dict(type='bool', default=False)
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
('state', 'present', ['email']),
|
||||
]
|
||||
)
|
||||
|
||||
api = NetBirdAPI(
|
||||
module,
|
||||
module.params['api_url'],
|
||||
module.params['api_token'],
|
||||
module.params['validate_certs']
|
||||
)
|
||||
|
||||
state = module.params['state']
|
||||
invite_id = module.params['invite_id']
|
||||
email = module.params['email']
|
||||
name = module.params['name']
|
||||
role = module.params['role']
|
||||
auto_groups = module.params['auto_groups']
|
||||
expires_in = module.params['expires_in']
|
||||
regenerate = module.params['regenerate']
|
||||
|
||||
result = dict(
|
||||
changed=False,
|
||||
invite={}
|
||||
)
|
||||
|
||||
try:
|
||||
if state == 'absent':
|
||||
if invite_id:
|
||||
if not module.check_mode:
|
||||
api.delete_user_invite(invite_id)
|
||||
result['changed'] = True
|
||||
result['msg'] = 'User invite deleted successfully'
|
||||
elif email:
|
||||
existing_invite = find_invite_by_email(api, email)
|
||||
if existing_invite:
|
||||
if not module.check_mode:
|
||||
api.delete_user_invite(existing_invite['id'])
|
||||
result['changed'] = True
|
||||
result['msg'] = 'User invite deleted successfully'
|
||||
module.exit_json(**result)
|
||||
|
||||
# state == 'present'
|
||||
existing_invite = find_invite_by_email(api, email)
|
||||
|
||||
if existing_invite:
|
||||
if regenerate:
|
||||
# Regenerate the invite token
|
||||
if not module.check_mode:
|
||||
invite, _ = api.regenerate_user_invite(
|
||||
existing_invite['id'],
|
||||
expires_in=expires_in
|
||||
)
|
||||
result['invite'] = invite
|
||||
else:
|
||||
result['invite'] = existing_invite
|
||||
result['changed'] = True
|
||||
else:
|
||||
# Invite already exists, return it
|
||||
result['invite'] = existing_invite
|
||||
result['msg'] = 'User invite already exists'
|
||||
else:
|
||||
# Create new invite
|
||||
if not module.check_mode:
|
||||
invite, _ = api.create_user_invite(
|
||||
email=email,
|
||||
name=name,
|
||||
role=role,
|
||||
auto_groups=auto_groups,
|
||||
expires_in=expires_in
|
||||
)
|
||||
result['invite'] = invite
|
||||
result['changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
except NetBirdAPIError as e:
|
||||
module.fail_json(msg=str(e), status_code=e.status_code, response=e.response)
|
||||
|
||||
|
||||
def main():
|
||||
run_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -72,6 +72,11 @@ options:
|
||||
- Whether to masquerade (NAT) traffic through this router.
|
||||
type: bool
|
||||
default: false
|
||||
enabled:
|
||||
description:
|
||||
- Whether the router is enabled.
|
||||
type: bool
|
||||
default: true
|
||||
resources:
|
||||
description:
|
||||
- List of resources (network addresses, CIDRs, or domains) for this network.
|
||||
@@ -348,6 +353,8 @@ def router_needs_update(current, desired):
|
||||
return True
|
||||
if current.get('masquerade') != desired.get('masquerade', False):
|
||||
return True
|
||||
if current.get('enabled') != desired.get('enabled', True):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -399,7 +406,8 @@ def sync_routers(api, module, network_id, desired_routers):
|
||||
peer_id=peer if peer else None,
|
||||
peer_groups=peer_groups,
|
||||
metric=desired.get('metric', 9999),
|
||||
masquerade=desired.get('masquerade', False)
|
||||
masquerade=desired.get('masquerade', False),
|
||||
enabled=desired.get('enabled', True)
|
||||
)
|
||||
final_routers.append(updated)
|
||||
else:
|
||||
@@ -415,7 +423,8 @@ def sync_routers(api, module, network_id, desired_routers):
|
||||
peer_id=peer if peer else None,
|
||||
peer_groups=peer_groups,
|
||||
metric=desired.get('metric', 9999),
|
||||
masquerade=desired.get('masquerade', False)
|
||||
masquerade=desired.get('masquerade', False),
|
||||
enabled=desired.get('enabled', True)
|
||||
)
|
||||
final_routers.append(created)
|
||||
changed = True
|
||||
@@ -503,7 +512,8 @@ def run_module():
|
||||
peer=dict(type='str'),
|
||||
peer_groups=dict(type='list', elements='str'),
|
||||
metric=dict(type='int', default=9999),
|
||||
masquerade=dict(type='bool', default=False)
|
||||
masquerade=dict(type='bool', default=False),
|
||||
enabled=dict(type='bool', default=True)
|
||||
),
|
||||
required_one_of=[('peer', 'peer_groups')],
|
||||
mutually_exclusive=[('peer', 'peer_groups')]
|
||||
|
||||
@@ -52,6 +52,11 @@ options:
|
||||
description:
|
||||
- Whether approval is required for the peer.
|
||||
type: bool
|
||||
ip:
|
||||
description:
|
||||
- The IP address to assign to the peer within the NetBird network.
|
||||
- Allows reassigning a peer's IP.
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- community.ansible_netbird.netbird
|
||||
requirements:
|
||||
@@ -167,8 +172,8 @@ from ansible_collections.community.ansible_netbird.plugins.module_utils.netbird_
|
||||
|
||||
def peer_needs_update(current, params):
|
||||
"""Check if peer needs to be updated."""
|
||||
for key in ['name', 'ssh_enabled', 'login_expiration_enabled',
|
||||
'inactivity_expiration_enabled', 'approval_required']:
|
||||
for key in ['name', 'ssh_enabled', 'login_expiration_enabled',
|
||||
'inactivity_expiration_enabled', 'approval_required', 'ip']:
|
||||
if params.get(key) is not None:
|
||||
if current.get(key) != params[key]:
|
||||
return True
|
||||
@@ -185,7 +190,8 @@ def run_module():
|
||||
ssh_enabled=dict(type='bool'),
|
||||
login_expiration_enabled=dict(type='bool'),
|
||||
inactivity_expiration_enabled=dict(type='bool'),
|
||||
approval_required=dict(type='bool')
|
||||
approval_required=dict(type='bool'),
|
||||
ip=dict(type='str')
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
@@ -232,9 +238,10 @@ def run_module():
|
||||
'ssh_enabled': module.params['ssh_enabled'],
|
||||
'login_expiration_enabled': module.params['login_expiration_enabled'],
|
||||
'inactivity_expiration_enabled': module.params['inactivity_expiration_enabled'],
|
||||
'approval_required': module.params['approval_required']
|
||||
'approval_required': module.params['approval_required'],
|
||||
'ip': module.params['ip']
|
||||
}
|
||||
|
||||
|
||||
if peer_needs_update(existing_peer, update_params):
|
||||
if not module.check_mode:
|
||||
peer, _ = api.update_peer(
|
||||
@@ -243,7 +250,8 @@ def run_module():
|
||||
ssh_enabled=module.params['ssh_enabled'],
|
||||
login_expiration_enabled=module.params['login_expiration_enabled'],
|
||||
inactivity_expiration_enabled=module.params['inactivity_expiration_enabled'],
|
||||
approval_required=module.params['approval_required']
|
||||
approval_required=module.params['approval_required'],
|
||||
ip=module.params['ip']
|
||||
)
|
||||
result['peer'] = peer
|
||||
else:
|
||||
|
||||
@@ -45,6 +45,11 @@ options:
|
||||
- Whether the policy is enabled.
|
||||
type: bool
|
||||
default: true
|
||||
source_posture_checks:
|
||||
description:
|
||||
- List of posture check IDs applied to policy source groups.
|
||||
type: list
|
||||
elements: str
|
||||
rules:
|
||||
description:
|
||||
- List of policy rules.
|
||||
@@ -82,7 +87,7 @@ options:
|
||||
default: true
|
||||
protocol:
|
||||
description:
|
||||
- Network protocol (all, tcp, udp, icmp).
|
||||
- Network protocol (all, tcp, udp, icmp, netbird-ssh).
|
||||
type: str
|
||||
default: all
|
||||
ports:
|
||||
@@ -90,6 +95,53 @@ options:
|
||||
- List of destination ports (e.g., ["80", "443", "8000-9000"]).
|
||||
type: list
|
||||
elements: str
|
||||
port_ranges:
|
||||
description:
|
||||
- List of port ranges.
|
||||
- Each range has start and end port numbers.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
start:
|
||||
description:
|
||||
- Start port number.
|
||||
type: int
|
||||
required: true
|
||||
end:
|
||||
description:
|
||||
- End port number.
|
||||
type: int
|
||||
required: true
|
||||
destination_resource:
|
||||
description:
|
||||
- Destination network resource for the rule.
|
||||
type: dict
|
||||
suboptions:
|
||||
id:
|
||||
description:
|
||||
- Resource ID.
|
||||
type: str
|
||||
required: true
|
||||
type:
|
||||
description:
|
||||
- Resource type.
|
||||
type: str
|
||||
required: true
|
||||
source_resource:
|
||||
description:
|
||||
- Source network resource for the rule.
|
||||
type: dict
|
||||
suboptions:
|
||||
id:
|
||||
description:
|
||||
- Resource ID.
|
||||
type: str
|
||||
required: true
|
||||
type:
|
||||
description:
|
||||
- Resource type.
|
||||
type: str
|
||||
required: true
|
||||
action:
|
||||
description:
|
||||
- Action to take (accept, drop).
|
||||
@@ -194,6 +246,43 @@ def find_policy_by_name(api, name):
|
||||
return None
|
||||
|
||||
|
||||
def build_rule_data(rule):
|
||||
"""Build rule payload for the API from Ansible rule config."""
|
||||
rule_data = {}
|
||||
if rule.get('name') is not None:
|
||||
rule_data['name'] = rule['name']
|
||||
if rule.get('description') is not None:
|
||||
rule_data['description'] = rule['description']
|
||||
if rule.get('enabled') is not None:
|
||||
rule_data['enabled'] = rule['enabled']
|
||||
if rule.get('sources') is not None:
|
||||
rule_data['sources'] = rule['sources']
|
||||
if rule.get('destinations') is not None:
|
||||
rule_data['destinations'] = rule['destinations']
|
||||
if rule.get('bidirectional') is not None:
|
||||
rule_data['bidirectional'] = rule['bidirectional']
|
||||
if rule.get('protocol') is not None:
|
||||
rule_data['protocol'] = rule['protocol']
|
||||
if rule.get('ports') is not None:
|
||||
rule_data['ports'] = rule['ports']
|
||||
if rule.get('port_ranges') is not None:
|
||||
rule_data['port_ranges'] = rule['port_ranges']
|
||||
if rule.get('destination_resource') is not None:
|
||||
rule_data['destinationResource'] = rule['destination_resource']
|
||||
if rule.get('source_resource') is not None:
|
||||
rule_data['sourceResource'] = rule['source_resource']
|
||||
if rule.get('action') is not None:
|
||||
rule_data['action'] = rule['action']
|
||||
return rule_data
|
||||
|
||||
|
||||
def build_rules_data(rules):
|
||||
"""Build list of rule payloads for the API."""
|
||||
if rules is None:
|
||||
return None
|
||||
return [build_rule_data(rule) for rule in rules]
|
||||
|
||||
|
||||
def policy_needs_update(current, params):
|
||||
"""Check if policy needs to be updated."""
|
||||
if params.get('name') is not None and current.get('name') != params['name']:
|
||||
@@ -202,6 +291,8 @@ def policy_needs_update(current, params):
|
||||
return True
|
||||
if params.get('enabled') is not None and current.get('enabled') != params['enabled']:
|
||||
return True
|
||||
if params.get('source_posture_checks') is not None and current.get('source_posture_checks') != params['source_posture_checks']:
|
||||
return True
|
||||
# For rules, always update if provided to ensure they match exactly
|
||||
if params.get('rules') is not None:
|
||||
return True
|
||||
@@ -217,7 +308,30 @@ def run_module():
|
||||
name=dict(type='str'),
|
||||
description=dict(type='str', default=''),
|
||||
enabled=dict(type='bool', default=True),
|
||||
rules=dict(type='list', elements='dict')
|
||||
source_posture_checks=dict(type='list', elements='str'),
|
||||
rules=dict(type='list', elements='dict', options=dict(
|
||||
name=dict(type='str'),
|
||||
description=dict(type='str'),
|
||||
enabled=dict(type='bool', default=True),
|
||||
sources=dict(type='list', elements='str'),
|
||||
destinations=dict(type='list', elements='str'),
|
||||
bidirectional=dict(type='bool', default=True),
|
||||
protocol=dict(type='str', default='all'),
|
||||
ports=dict(type='list', elements='str'),
|
||||
port_ranges=dict(type='list', elements='dict', options=dict(
|
||||
start=dict(type='int', required=True),
|
||||
end=dict(type='int', required=True)
|
||||
)),
|
||||
destination_resource=dict(type='dict', options=dict(
|
||||
id=dict(type='str', required=True),
|
||||
type=dict(type='str', required=True)
|
||||
)),
|
||||
source_resource=dict(type='dict', options=dict(
|
||||
id=dict(type='str', required=True),
|
||||
type=dict(type='str', required=True)
|
||||
)),
|
||||
action=dict(type='str', default='accept')
|
||||
))
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
@@ -240,7 +354,8 @@ def run_module():
|
||||
name = module.params['name']
|
||||
description = module.params['description']
|
||||
enabled = module.params['enabled']
|
||||
rules = module.params['rules']
|
||||
source_posture_checks = module.params['source_posture_checks']
|
||||
rules = build_rules_data(module.params['rules'])
|
||||
|
||||
result = dict(
|
||||
changed=False,
|
||||
@@ -274,6 +389,7 @@ def run_module():
|
||||
'name': name,
|
||||
'description': description,
|
||||
'enabled': enabled,
|
||||
'source_posture_checks': source_posture_checks,
|
||||
'rules': rules
|
||||
}
|
||||
|
||||
@@ -284,7 +400,8 @@ def run_module():
|
||||
name=name,
|
||||
enabled=enabled,
|
||||
description=description,
|
||||
rules=rules
|
||||
rules=rules,
|
||||
source_posture_checks=source_posture_checks
|
||||
)
|
||||
result['policy'] = policy
|
||||
else:
|
||||
@@ -302,7 +419,8 @@ def run_module():
|
||||
name=name,
|
||||
enabled=enabled,
|
||||
description=description,
|
||||
rules=rules or []
|
||||
rules=rules or [],
|
||||
source_posture_checks=source_posture_checks
|
||||
)
|
||||
result['policy'] = policy
|
||||
result['changed'] = True
|
||||
|
||||
@@ -278,7 +278,6 @@ def run_module():
|
||||
if not module.check_mode:
|
||||
key, _ = api.update_setup_key(
|
||||
existing_key['id'],
|
||||
name=name,
|
||||
revoked=module.params['revoked'],
|
||||
auto_groups=module.params['auto_groups']
|
||||
)
|
||||
|
||||
@@ -24,7 +24,6 @@ options:
|
||||
- The desired state of the user.
|
||||
type: str
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
user_id:
|
||||
description:
|
||||
- The unique identifier of the user.
|
||||
@@ -60,6 +59,14 @@ options:
|
||||
description:
|
||||
- If set to true, the user is blocked and cannot use the system.
|
||||
type: bool
|
||||
action:
|
||||
description:
|
||||
- Action to perform on the user.
|
||||
- Use 'approve' to approve a user with pending approval status.
|
||||
- Use 'reject' to reject a user with pending approval status.
|
||||
- Mutually exclusive with state.
|
||||
type: str
|
||||
choices: ['approve', 'reject']
|
||||
resend_invitation:
|
||||
description:
|
||||
- Resend user invitation email.
|
||||
@@ -193,7 +200,7 @@ def run_module():
|
||||
"""Main module execution."""
|
||||
argument_spec = netbird_argument_spec()
|
||||
argument_spec.update(
|
||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
state=dict(type='str', choices=['present', 'absent']),
|
||||
user_id=dict(type='str'),
|
||||
email=dict(type='str'),
|
||||
name=dict(type='str'),
|
||||
@@ -201,7 +208,8 @@ def run_module():
|
||||
auto_groups=dict(type='list', elements='str', default=[]),
|
||||
is_service_user=dict(type='bool', default=False),
|
||||
is_blocked=dict(type='bool'),
|
||||
resend_invitation=dict(type='bool', default=False)
|
||||
resend_invitation=dict(type='bool', default=False),
|
||||
action=dict(type='str', choices=['approve', 'reject']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
@@ -209,7 +217,9 @@ def run_module():
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
('state', 'absent', ['user_id']),
|
||||
]
|
||||
],
|
||||
mutually_exclusive=[['state', 'action']],
|
||||
required_one_of=[['state', 'action']]
|
||||
)
|
||||
|
||||
api = NetBirdAPI(
|
||||
@@ -235,6 +245,21 @@ def run_module():
|
||||
)
|
||||
|
||||
try:
|
||||
# Handle action parameter (approve/reject)
|
||||
action = module.params.get('action')
|
||||
if action:
|
||||
user_id = module.params['user_id']
|
||||
if not user_id:
|
||||
module.fail_json(msg="user_id is required when using action parameter")
|
||||
if not module.check_mode:
|
||||
if action == 'approve':
|
||||
api.approve_user(user_id)
|
||||
elif action == 'reject':
|
||||
api.reject_user(user_id)
|
||||
result['changed'] = True
|
||||
result['msg'] = f'User {action}d successfully'
|
||||
module.exit_json(**result)
|
||||
|
||||
# Handle resend invitation
|
||||
if resend_invitation and user_id:
|
||||
if not module.check_mode:
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
# tasks/identity_providers.yml - Identity provider management tasks
|
||||
|
||||
- name: Manage NetBird identity providers
|
||||
community.ansible_netbird.netbird_idp:
|
||||
api_url: "{{ netbird_api_url }}"
|
||||
api_token: "{{ netbird_api_token }}"
|
||||
validate_certs: "{{ netbird_validate_certs }}"
|
||||
name: "{{ item.name }}"
|
||||
type: "{{ item.type }}"
|
||||
issuer: "{{ item.issuer }}"
|
||||
client_id: "{{ item.client_id }}"
|
||||
client_secret: "{{ item.client_secret }}"
|
||||
state: "{{ item.state | default('present') }}"
|
||||
loop: "{{ netbird_identity_providers }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
when: netbird_identity_providers | length > 0
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
# tasks/invites.yml - User invite management tasks
|
||||
|
||||
- name: Manage NetBird user invites
|
||||
community.ansible_netbird.netbird_invite:
|
||||
api_url: "{{ netbird_api_url }}"
|
||||
api_token: "{{ netbird_api_token }}"
|
||||
validate_certs: "{{ netbird_validate_certs }}"
|
||||
email: "{{ item.email }}"
|
||||
name: "{{ item.name | default(omit) }}"
|
||||
role: "{{ item.role | default('user') }}"
|
||||
auto_groups: "{{ item.auto_groups | default([]) }}"
|
||||
expires_in: "{{ item.expires_in | default(omit) }}"
|
||||
regenerate: "{{ item.regenerate | default(false) }}"
|
||||
state: "{{ item.state | default('present') }}"
|
||||
loop: "{{ netbird_invites }}"
|
||||
loop_control:
|
||||
label: "{{ item.email }}"
|
||||
when: netbird_invites | length > 0
|
||||
@@ -64,3 +64,17 @@
|
||||
- netbird
|
||||
- netbird-posture-checks
|
||||
|
||||
- name: Include identity provider management tasks
|
||||
ansible.builtin.include_tasks: identity_providers.yml
|
||||
when: netbird_identity_providers | length > 0
|
||||
tags:
|
||||
- netbird
|
||||
- netbird-identity-providers
|
||||
|
||||
- name: Include user invite management tasks
|
||||
ansible.builtin.include_tasks: invites.yml
|
||||
when: netbird_invites | length > 0
|
||||
tags:
|
||||
- netbird
|
||||
- netbird-invites
|
||||
|
||||
|
||||
Reference in New Issue
Block a user