-
Notifications
You must be signed in to change notification settings - Fork 9
feat(vault): add fine-grained app policies and preserve hub-role #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| --- | ||
| # vault_app_policies.yaml | ||
| # Creates fine-grained Vault policies for application-level isolation | ||
| # | ||
| # This task file creates individual policies for each vaultPrefix provided. | ||
| # Each application gets its own policy that only allows access to its | ||
| # specific path, enabling application-level secret isolation. | ||
| # | ||
| # Required variables: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add these variables and their descriptions to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added all app policy variables to |
||
| # app_prefixes: List of app prefixes to create policies for | ||
| # e.g., ["apps/qtodo", "hub/infra/keycloak"] | ||
| # | ||
| # Optional variables: | ||
| # app_capabilities: Capabilities for app policies (default: read) | ||
| # app_update_hub_role: Whether to update hub-role with new policies (default: true) | ||
| # app_create_jwt_roles: Whether to create JWT roles per app (default: false) | ||
| # app_jwt_role_config: Dict mapping app names to JWT role config | ||
| # | ||
| # Example usage: | ||
| # - name: Create app-specific vault policies | ||
| # ansible.builtin.include_tasks: | ||
| # file: vault_app_policies.yaml | ||
| # vars: | ||
| # app_prefixes: | ||
| # - apps/qtodo | ||
| # - hub/infra/keycloak | ||
| # app_update_hub_role: true | ||
|
|
||
| - name: Debug - Show app prefixes to process | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand that these debug messages are useful during the development phase, do we need them during the execution phase?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point! Added |
||
| ansible.builtin.debug: | ||
| msg: "Processing app prefixes: {{ app_prefixes }}" | ||
| when: app_prefixes is defined and app_prefixes | length > 0 | ||
|
|
||
| - name: Skip if no app prefixes defined | ||
| ansible.builtin.debug: | ||
| msg: "No app_prefixes defined, skipping app policy creation" | ||
| when: app_prefixes is not defined or app_prefixes | length == 0 | ||
|
|
||
| # Create policy template for each app prefix | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of creating a file with an echo, let's use the k8s_cp, copy or template module if possible
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refactored to pipe policy content directly to |
||
| - name: Configure app policy template | ||
| kubernetes.core.k8s_exec: | ||
| namespace: "{{ vault_ns }}" | ||
| pod: "{{ vault_pod }}" | ||
| command: > | ||
| bash -e -c "echo 'path \"secret/data/{{ item }}/*\" { capabilities = [\"read\"] }' > /tmp/policy-{{ item | replace('/', '-') }}.hcl" | ||
| loop: "{{ app_prefixes }}" | ||
| loop_control: | ||
| label: "Creating policy template for {{ item }}" | ||
| when: app_prefixes is defined and app_prefixes | length > 0 | ||
|
|
||
| # Write the policy to Vault | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're writing policies whether they exist or not. One of Ansible's characteristics is idempotence. It should only run when a change is needed. Existing policies should be validated first, and then, based on the current state, new policies should be written.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added idempotency check. The task now queries existing Vault policies first ( |
||
| - name: Configure app policy in Vault | ||
| kubernetes.core.k8s_exec: | ||
| namespace: "{{ vault_ns }}" | ||
| pod: "{{ vault_pod }}" | ||
| command: > | ||
| vault policy write {{ item | replace('/', '-') }}-k8s-secret | ||
| /tmp/policy-{{ item | replace('/', '-') }}.hcl | ||
| loop: "{{ app_prefixes }}" | ||
| loop_control: | ||
| label: "Writing policy {{ item | replace('/', '-') }}-k8s-secret" | ||
| when: app_prefixes is defined and app_prefixes | length > 0 | ||
|
|
||
| # Build list of new policy names | ||
| - name: Build app policy names list | ||
| ansible.builtin.set_fact: | ||
| _app_policy_names: "{{ app_prefixes | map('replace', '/', '-') | map('regex_replace', '$', '-k8s-secret') | list }}" | ||
| when: app_prefixes is defined and app_prefixes | length > 0 | ||
|
|
||
| # Get current hub-role policies | ||
| - name: Get current hub-role policies | ||
| kubernetes.core.k8s_exec: | ||
| namespace: "{{ vault_ns }}" | ||
| pod: "{{ vault_pod }}" | ||
| command: > | ||
| vault read -format=json auth/{{ vault_hub }}/role/{{ vault_hub }}-role | ||
| register: _hub_role_result | ||
| failed_when: false | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a command that doesn't change anything (it only reads), we might want to add
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
| when: | ||
| - app_prefixes is defined and app_prefixes | length > 0 | ||
| - app_update_hub_role | default(true) | bool | ||
|
|
||
| # Parse current policies and merge with new ones | ||
| - name: Set current policies fact | ||
| ansible.builtin.set_fact: | ||
| _current_policies: "{{ (_hub_role_result.stdout | from_json).data.token_policies | default(['default', vault_global_policy ~ '-secret', vault_pushsecrets_policy ~ '-secret', vault_hub ~ '-secret']) }}" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of hardcoding the default values manually on this line, it's better to put them in a variable in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! Moved the default policies to |
||
| when: | ||
| - app_prefixes is defined and app_prefixes | length > 0 | ||
| - app_update_hub_role | default(true) | bool | ||
| - _hub_role_result.rc == 0 | ||
|
|
||
| - name: Set default policies if hub-role not found | ||
| ansible.builtin.set_fact: | ||
| _current_policies: "{{ ['default', vault_global_policy ~ '-secret', vault_pushsecrets_policy ~ '-secret', vault_hub ~ '-secret'] }}" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's move this to a variable in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done: both instances now reference |
||
| when: | ||
| - app_prefixes is defined and app_prefixes | length > 0 | ||
| - app_update_hub_role | default(true) | bool | ||
| - _hub_role_result.rc != 0 | ||
|
|
||
| # Merge current and new policies (removing duplicates) | ||
| - name: Merge policies | ||
| ansible.builtin.set_fact: | ||
| _merged_policies: "{{ (_current_policies + _app_policy_names) | unique }}" | ||
| when: | ||
| - app_prefixes is defined and app_prefixes | length > 0 | ||
| - app_update_hub_role | default(true) | bool | ||
|
|
||
| # Update hub-role with merged policies | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We execute this unconditionally, whether the policies are already configured or not?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added idempotency check. The hub-role is now only updated whent he merged policies differ from the current policies. Sorted comparison is used to handle order differences. |
||
| - name: Update hub-role with app policies | ||
| kubernetes.core.k8s_exec: | ||
| namespace: "{{ vault_ns }}" | ||
| pod: "{{ vault_pod }}" | ||
| command: > | ||
| vault write auth/{{ vault_hub }}/role/{{ vault_hub }}-role | ||
| bound_service_account_names="{{ active_external_secrets_sa | default('golang-external-secrets') }}" | ||
| bound_service_account_namespaces="{{ active_external_secrets_ns | default('golang-external-secrets') }}" | ||
| policies="{{ _merged_policies | join(',') }}" | ||
| ttl="{{ vault_hub_ttl }}" | ||
| when: | ||
| - app_prefixes is defined and app_prefixes | length > 0 | ||
| - app_update_hub_role | default(true) | bool | ||
|
|
||
| - name: Display updated hub-role policies | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Debug messages are useful during development, but we should remove it in execution
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Debug messages are now gated with |
||
| ansible.builtin.debug: | ||
| msg: "hub-role policies updated to: {{ _merged_policies | join(', ') }}" | ||
| when: | ||
| - app_prefixes is defined and app_prefixes | length > 0 | ||
| - app_update_hub_role | default(true) | bool | ||
|
|
||
| # Optionally create JWT roles for app-level isolation | ||
| # This requires app_jwt_role_config to be defined | ||
| - name: Configure JWT role for app (if configured) | ||
| kubernetes.core.k8s_exec: | ||
| namespace: "{{ vault_ns }}" | ||
| pod: "{{ vault_pod }}" | ||
| command: > | ||
| vault write auth/"{{ vault_hub }}"/role/"{{ _app_name }}"-role | ||
| bound_service_account_names="{{ _role_config.service_account_names }}" | ||
| bound_service_account_namespaces="{{ _role_config.service_account_namespaces }}" | ||
| policies="default,{{ vault_global_policy }}-secret,{{ item | replace('/', '-') }}-k8s-secret" | ||
| ttl="{{ _role_config.ttl | default(vault_hub_ttl) }}" | ||
| loop: "{{ app_prefixes }}" | ||
| loop_control: | ||
| label: "Creating JWT role for {{ _app_name }}" | ||
| vars: | ||
| _app_name: "{{ item | basename }}" | ||
| _role_config: "{{ app_jwt_role_config[_app_name] | default({}) }}" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of using an alternative dictionary with role configuration values and a key that matches the app name, wouldn't it be better to have
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refactored |
||
| when: | ||
| - app_prefixes is defined and app_prefixes | length > 0 | ||
| - app_create_jwt_roles | default(false) | bool | ||
| - app_jwt_role_config is defined | ||
| - _app_name in app_jwt_role_config | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,6 +102,19 @@ | |
| not jwt_config_oidc_discovery_url == oidc_discovery_url or | ||
| not jwt_config_default_role == default_role | default('default') | ||
|
|
||
| # Create JWT auth policies from vault_jwt_policies variable | ||
| # These are manually defined policies for SPIFFE workloads that need direct Vault access | ||
| - name: Write JWT policies directly to Vault | ||
| kubernetes.core.k8s_exec: | ||
| namespace: "{{ vault_ns }}" | ||
| pod: "{{ vault_pod }}" | ||
| command: > | ||
| bash -e -c "echo '{{ item.policy | b64encode }}' | base64 -d | vault policy write {{ item.name }} -" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I imagine this base64 encoding and decoding issue is due to escape characters, but we should be able to solve it with escape characters or by creating HCL files as is done in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point! Replaced the base64 encode/decode with a heredoc which handles escape characters naturally and is more readable. |
||
| loop: "{{ vault_jwt_policies | default([]) }}" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In addition to adding the variable
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
| loop_control: | ||
| label: "Writing JWT policy {{ item.name }}" | ||
| when: vault_jwt_policies is defined and vault_jwt_policies | length > 0 | ||
|
|
||
| - name: Run JWT role tasks | ||
| ansible.builtin.include_tasks: vault_jwt_roles.yaml | ||
| loop: "{{ vault_jwt_roles | default([]) }}" | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -130,12 +130,48 @@ | |||||||
| pod: "{{ vault_pod }}" | ||||||||
| command: "vault policy write {{ vault_hub }}-secret /tmp/policy-{{ vault_hub }}.hcl" | ||||||||
|
|
||||||||
| - name: Configure kubernetes role for hub | ||||||||
| # Get current hub-role policies to preserve any custom policies added by patterns | ||||||||
| - name: Get current hub-role policies | ||||||||
| kubernetes.core.k8s_exec: | ||||||||
| namespace: "{{ vault_ns }}" | ||||||||
| pod: "{{ vault_pod }}" | ||||||||
| command: > | ||||||||
| vault read -format=json auth/{{ vault_hub }}/role/{{ vault_hub }}-role | ||||||||
| register: _hub_role_result | ||||||||
| failed_when: false | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
|
|
||||||||
| # Define the default policies that VP framework requires | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd move this to the role's defaults file (
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved the default policies to defaults/main.yml |
||||||||
| - name: Set default hub-role policies list | ||||||||
| ansible.builtin.set_fact: | ||||||||
| _default_hub_policies: | ||||||||
| - default | ||||||||
| - "{{ vault_global_policy }}-secret" | ||||||||
| - "{{ vault_pushsecrets_policy }}-secret" | ||||||||
| - "{{ vault_hub }}-secret" | ||||||||
|
|
||||||||
| # Get existing policies (empty list if role doesn't exist yet) | ||||||||
| - name: Set current policies fact from existing hub-role | ||||||||
| ansible.builtin.set_fact: | ||||||||
| _current_hub_policies: "{{ (_hub_role_result.stdout | from_json).data.token_policies | default([]) }}" | ||||||||
| when: _hub_role_result.rc == 0 | ||||||||
|
|
||||||||
| - name: Set empty current policies if hub-role not found | ||||||||
| ansible.builtin.set_fact: | ||||||||
| _current_hub_policies: [] | ||||||||
| when: _hub_role_result.rc != 0 | ||||||||
|
|
||||||||
| # Merge existing policies with defaults (preserves custom policies, ensures defaults present) | ||||||||
| - name: Merge hub-role policies | ||||||||
| ansible.builtin.set_fact: | ||||||||
| _merged_hub_policies: "{{ (_current_hub_policies + _default_hub_policies) | unique }}" | ||||||||
|
|
||||||||
| - name: Configure kubernetes role for hub with merged policies | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we're going to check existing policies, we could take advantage of this and add a conditional to this execution and only run the command when the policies have changed or the role is created for the first time.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The task checks |
||||||||
| kubernetes.core.k8s_exec: | ||||||||
| namespace: "{{ vault_ns }}" | ||||||||
| pod: "{{ vault_pod }}" | ||||||||
| command: > | ||||||||
| vault write auth/"{{ vault_hub }}"/role/"{{ vault_hub }}"-role | ||||||||
| bound_service_account_names="{{ active_external_secrets_sa }}" | ||||||||
| bound_service_account_namespaces="{{ active_external_secrets_ns }}" | ||||||||
| policies="default,{{ vault_global_policy }}-secret,{{ vault_pushsecrets_policy }}-secret,{{ vault_hub }}-secret" ttl="{{ vault_hub_ttl }}" | ||||||||
| policies="{{ _merged_hub_policies | join(',') }}" | ||||||||
| ttl="{{ vault_hub_ttl }}" | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When are these tasks executed? I haven't been able to find the include
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad. I only included this file in my local testing ansible tasks which is outside of this repo. Now I have this file integrated into the
push_parsed_secrets.yamlflow. The task file is automatically included when processing secrets with customvaultPrefixvalues.