FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

microsoftexpert/terraform-azurerm-key-vault-access-policy: Terraform module: terraform-azurerm-key-vault-access-policy · GitHub

Repository navigation

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

☁️ Azure Key Vault Access Policy Terraform Module

A single Key Vault access policy that grants one principal least-privilege permissions over a vault's keys, secrets, certificates, and managed-storage objects — targeting hashicorp/azurerm ~> 4.0.

🧩 Overview

This module manages a single azurerm_key_vault_access_policy on a Key Vault that uses the access-policy authorization model (not RBAC). It grants exactly one principal — a user, service principal, group, or managed identity — a scoped set of permissions over the vault's data planes:

  • 🔑 Key permissions — cryptographic operations and key lifecycle (Get, List, Create, Sign, WrapKey/UnwrapKey, Rotate, …).
  • 🔒 Secret permissions — secret read/write/lifecycle (Get, List, Set, Delete, …).
  • 📜 Certificate permissions — certificate management and issuer administration (Get, Create, Import, ManageContacts, …).
  • 🗄️ Managed-storage permissions — storage-account key and SAS management (Get, GetSAS, RegenerateKey, …).

Every permission list defaults to empty, so the empty call grants nothing. An all-empty policy is accepted rather than refused: the provider publishes no at-least-one-permission requirement for this resource, so enforcing one here would reject input the provider accepts — and, because a failed validation {} blocks terraform destroy as well as apply, would strand anyone who already owns such a policy. The condition is reported instead, through the grants_no_access output.

⚠️ Its sibling terraform-azurerm-application-insights-api-key does the opposite, and correctly: there the provider does require at least one permission, in its Create function body, so the module mirrors that rule as a validation to move the refusal offline. Two grant resources, opposite answers — do not carry the assumption between them.

💡 Why it matters: Access policies are the classic Key Vault authorization model. Getting them wrong either locks out a workload or over-grants it. This module encodes the principal, tenant, and vault as immutable inputs and validates every permission string against its closed enum before Terraform ever calls Azure, so a typo fails at plan rather than surfacing as a runtime authorization gap.

❤️ Support this project

If this module saves you time, please consider supporting its continued development:

🗺️ Where this fits in the family

flowchart LR
  kv["terraform-azurerm-key-vault"]
  uai["terraform-azurerm-user-assigned-identity"]
  me["terraform-azurerm-key-vault-access-policy"]
  v["azurerm_key_vault_access_policy"]
  kv -->|"key_vault_id"| me
  uai -->|"object_id (principal)"| me
  me -->|"creates"| v
  classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
  classDef target fill:#004578,stroke:#002d4d,color:#ffffff;
  classDef ext fill:#f2f2f2,stroke:#c8c8c8,color:#111111;
  class me me;
  class v target;
  class kv,uai ext;
Loading

The Key Vault module supplies the key_vault_id; the user-assigned identity module (or any caller-supplied principal) supplies the object_id. This module owns only the access policy record.

🧬 What this module builds

flowchart LR
  in_id["key_vault_id / tenant_id / object_id / application_id"]
  in_perm["key / secret / certificate / storage permissions"]
  res["azurerm_key_vault_access_policy.this"]
  out_id["id"]
  in_id -->|"input"| res
  in_perm -->|"input"| res
  res -->|"output"| out_id
  classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
  class res me;
Loading

Resource inventory

Resource Name Cardinality Role
azurerm_key_vault_access_policy this single (keystone) Grants one principal scoped permissions on the vault.
timeouts (nested block) — optional Per-operation create/read/update/delete timeouts.

✅ Provider / Versions

Requirement Value
Terraform >= 1.12.0
Provider hashicorp/azurerm ~> 4.0
Provider block None in this module — the caller configures provider "azurerm", including the mandatory features {} block, plus authentication, subscription, and tenant.

Schema notes that bite

  • key_vault_id, tenant_id, object_id, and application_id are all force-new — changing any of them destroys and recreates the policy.
  • object_id must be unique across the vault's access policies. Two policies for the same principal on the same vault conflict.
  • The target vault must use the access-policy model (enable_rbac_authorization = false). An RBAC-enabled vault rejects access policies outright.
  • Do not mix this module with inline access_policy blocks declared on the azurerm_key_vault resource itself. The two ownership models fight each other and produce perpetual diffs. Pick one model per vault.
  • The emitted id is unique to Terraform's state and does not map to a distinct Azure object — an access policy is a sub-record of the vault, not a standalone ARM resource.

🔑 Required Azure RBAC Roles / Permissions

Least-privilege at the Key Vault scope, on the caller's identity:

  • Microsoft.KeyVault/vaults/accessPolicies/write
  • Microsoft.KeyVault/vaults/read

The built-in Key Vault Contributor role (management plane) covers editing access policies.

🧰 Azure Prerequisites

  • An existing Key Vault using the access-policy authorization model (not RBAC).
  • The object ID of the principal being granted access, and the tenant ID.
  • The Microsoft.KeyVault resource provider registered on the subscription.

📁 Module Structure

terraform-azurerm-key-vault-access-policy/
├── providers.tf    # required_version + azurerm ~> 4.0 pin; no provider block
├── variables.tf    # typed inputs: identity, four permission lists, timeouts
├── main.tf         # keystone azurerm_key_vault_access_policy.this + timeouts block
├── outputs.tf      # id (Terraform-unique)
├── README.md       # this document
├── SCOPE.md        # cross-module contract
├── LICENSE         # MIT
└── .gitignore      # canonical library ignore set

⚙️ Quick Start

provider "azurerm" {
  features {}
}

module "app_secret_reader" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id = azurerm_key_vault.example.id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = azurerm_user_assigned_identity.app.principal_id

  secret_permissions = ["Get", "List"]
}

ℹ️ The caller owns the provider block, authentication, and the mandatory features {} block. This module never declares a provider.

🔌 Cross-Module Contract

Consumes

Input Type Typical source
key_vault_id string terraform-azurerm-key-vault (id)
object_id string terraform-azurerm-user-assigned-identity (principal_id) or a caller-supplied principal
tenant_id string caller / azurerm_client_config data source

Emits

Output Description Consumed by
id Resource ID of the access policy (Terraform-unique; does not map to a distinct Azure object). References / downstream depends_on sequencing.

📚 Example Library

1 · Secret read-only for a workload identity

The most common shape — a workload that reads connection strings and API keys but never writes them.

module "secret_reader" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id       = azurerm_key_vault.app.id
  tenant_id          = data.azurerm_client_config.current.tenant_id
  object_id          = azurerm_user_assigned_identity.app.principal_id
  secret_permissions = ["Get", "List"]
}

🔒 Grant only Get and List. A reader that cannot Set or Delete cannot tamper with secret material.

2 · Secret read/write for a provisioning identity

A CI or provisioning identity that seeds secrets needs write and lifecycle permissions.

module "secret_writer" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id       = azurerm_key_vault.app.id
  tenant_id          = data.azurerm_client_config.current.tenant_id
  object_id          = var.provisioner_object_id
  secret_permissions = ["Get", "List", "Set", "Delete", "Recover"]
}

⚠️ Recover matters when the vault has soft-delete enabled — without it, a name that was soft-deleted cannot be reclaimed by this principal.

3 · Key crypto for CMK wrap/unwrap

Customer-managed key (CMK) scenarios — a Storage account, disk, or SQL server that wraps its data encryption key with a Key Vault key needs exactly WrapKey, UnwrapKey, and Get.

module "cmk_consumer" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id    = azurerm_key_vault.cmk.id
  tenant_id       = data.azurerm_client_config.current.tenant_id
  object_id       = azurerm_storage_account.data.identity[0].principal_id
  key_permissions = ["Get", "WrapKey", "UnwrapKey"]
}

💡 This is the canonical CMK grant. The consuming service uses the key to wrap its own DEK; it never needs to export or delete the key itself.

4 · Key administration and rotation

A key operator that creates and rotates keys needs the full lifecycle plus rotation-policy management.

module "key_admin" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id = azurerm_key_vault.cmk.id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = var.key_operator_object_id

  key_permissions = [
    "Get", "List", "Create", "Import", "Update", "Delete", "Recover",
    "Rotate", "GetRotationPolicy", "SetRotationPolicy",
  ]
}

ℹ️ Rotate, GetRotationPolicy, and SetRotationPolicy are what let this principal drive automated key rotation — omit them for a principal that only consumes keys.

5 · Certificate management

A certificate operator managing TLS certificates and their issuers.

module "cert_manager" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id = azurerm_key_vault.tls.id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = var.cert_operator_object_id

  certificate_permissions = [
    "Get", "List", "Create", "Import", "Update", "Delete",
    "ManageContacts", "ManageIssuers", "GetIssuers", "ListIssuers", "SetIssuers",
  ]
}

🔒 Issuer permissions (ManageIssuers, SetIssuers, …) are only needed by principals that integrate with a CA. A principal that merely reads certificates needs just Get and List.

6 · Managed-storage permissions

Key Vault managed storage account keys — a principal that rotates storage account keys and issues SAS tokens through the vault.

module "storage_key_manager" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id        = azurerm_key_vault.ops.id
  tenant_id           = data.azurerm_client_config.current.tenant_id
  object_id           = var.storage_ops_object_id
  storage_permissions = ["Get", "List", "GetSAS", "SetSAS", "RegenerateKey"]
}

⚠️ RegenerateKey rotates the underlying storage account key on the vault's schedule. Grant it only to the identity responsible for storage-key lifecycle.

7 · Compound identity with application_id

Some legacy on-behalf-of scenarios require a compound identity — a service principal acting through a specific application. Supplying application_id alongside object_id scopes the policy to that pairing.

module "compound_identity" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id   = azurerm_key_vault.app.id
  tenant_id      = data.azurerm_client_config.current.tenant_id
  object_id      = var.principal_object_id
  application_id = var.application_object_id

  secret_permissions = ["Get"]
}

⚠️ application_id is force-new and rarely needed. Leave it null (the default) for standard user, group, or service-principal grants.

8 · Mixed keys and secrets for a single workload

A workload that both reads a secret and unwraps a CMK in one grant.

module "mixed_grant" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id       = azurerm_key_vault.app.id
  tenant_id          = data.azurerm_client_config.current.tenant_id
  object_id          = azurerm_user_assigned_identity.app.principal_id
  key_permissions    = ["Get", "UnwrapKey"]
  secret_permissions = ["Get", "List"]
}

💡 A single access policy can span multiple data planes. Keep each list minimal — grant keys OR secrets OR both, but only the verbs the workload actually calls.

9 · Break-glass administrator

An emergency human operator granted broad access across all four data planes. Reserve this for a tightly controlled break-glass group.

module "break_glass" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id = azurerm_key_vault.app.id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = var.break_glass_group_object_id

  key_permissions         = ["Get", "List", "Create", "Delete", "Recover", "Backup", "Restore"]
  secret_permissions      = ["Get", "List", "Set", "Delete", "Recover", "Backup", "Restore"]
  certificate_permissions = ["Get", "List", "Create", "Delete", "Recover", "Backup", "Restore"]
}

🔒 Broad grants belong to audited groups, not standing individual identities. Pair this with diagnostic logging on the vault so every operation is recorded.

10 · Backup and restore only

A backup service that needs to back up and restore vault objects but must not read their plaintext values.

module "backup_service" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id       = azurerm_key_vault.app.id
  tenant_id          = data.azurerm_client_config.current.tenant_id
  object_id          = var.backup_service_object_id
  key_permissions    = ["Backup", "Restore"]
  secret_permissions = ["Backup", "Restore"]
}

🔒 Backup returns an encrypted blob, not plaintext. A backup identity that lacks Get can move material between vaults without ever seeing the underlying secret.

11 · Custom timeouts for a slow-provisioning tenant

Override the default operation timeouts where a busy tenant is slow to apply access-policy changes.

module "with_timeouts" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id       = azurerm_key_vault.app.id
  tenant_id          = data.azurerm_client_config.current.tenant_id
  object_id          = var.principal_object_id
  secret_permissions = ["Get", "List"]

  timeouts = {
    create = "30m"
    delete = "30m"
  }
}

ℹ️ Leave timeouts null (the default) to use the provider defaults. Only set it when you have observed genuine timeouts.

12 · for_each over multiple principals

Grant the same secret-read permission to a keyed map of principals, so adding or removing one never re-indexes the rest.

variable "readers" {
  type = map(string) # key => principal object ID
  default = {
    api     = "00000000-0000-0000-0000-000000000001"
    worker  = "00000000-0000-0000-0000-000000000002"
    scanner = "00000000-0000-0000-0000-000000000003"
  }
}

module "secret_readers" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"
  for_each = var.readers

  key_vault_id       = azurerm_key_vault.app.id
  tenant_id          = data.azurerm_client_config.current.tenant_id
  object_id          = each.value
  secret_permissions = ["Get", "List"]
}

💡 Key the map by a stable role name, not by list index. Each object_id must be unique across the vault's policies, which map keys naturally enforce here.

13 · 🏗️ End-to-end composition

Wire a resource group, an access-policy-model Key Vault, and a user-assigned identity into this module — the full path from infrastructure to a scoped grant.

provider "azurerm" {
  features {}
}

data "azurerm_client_config" "current" {}

module "rg" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
  name     = "rg-vault-demo"
  location = "eastus2"
}

module "identity" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-user-assigned-identity.git?ref=v1.0.0"
  name                = "id-app-demo"
  resource_group_name = module.rg.name
  location            = "eastus2"
}

module "vault" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault.git?ref=v1.0.0"
  name                = "kv-demo-eus2"
  resource_group_name = module.rg.name
  location            = "eastus2"
  tenant_id           = data.azurerm_client_config.current.tenant_id

  # This module manages access policies, so the vault must use the access-policy model.
  rbac_authorization_enabled = false
}

module "app_secret_reader" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-key-vault-access-policy.git?ref=v1.0.0"

  key_vault_id       = module.vault.id
  tenant_id          = data.azurerm_client_config.current.tenant_id
  object_id          = module.identity.principal_id
  secret_permissions = ["Get", "List"]
}

🔒 The vault is created with enable_rbac_authorization = false so it honors access policies. Never also declare inline access_policy blocks on that vault — this module owns them.

📥 Inputs

Identity (required, all force-new except application_id)

  • key_vault_id — Resource ID of the target vault.
  • tenant_id — Entra ID tenant used to authenticate vault requests.
  • object_id — Object ID of the principal being granted access; unique per vault.
  • application_id — Optional Application (client) object ID for compound identity; defaults to null.

Permissions (each list(string), default [], element-validated against its enum)

  • key_permissions, secret_permissions, certificate_permissions, storage_permissions.
  • A cross-list check requires the total number of permissions across all four lists to be greater than zero.

Universal tail

  • timeouts — optional per-operation create/read/update/delete durations; defaults to null. This resource does not support tags, so the tail is timeouts only.
Full input schema
variable "key_vault_id" {
  type = string # force-new
}

variable "tenant_id" {
  type = string # force-new
}

variable "object_id" {
  type = string # force-new; unique across the vault's access policies
}

variable "application_id" {
  type    = string # force-new; compound identity
  default = null
}

variable "key_permissions" {
  type    = list(string)
  default = []
  # each element ∈ Backup, Create, Decrypt, Delete, Encrypt, Get, Import, List,
  #   Purge, Recover, Restore, Sign, UnwrapKey, Update, Verify, WrapKey, Release,
  #   Rotate, GetRotationPolicy, SetRotationPolicy
}

variable "secret_permissions" {
  type    = list(string)
  default = []
  # each element ∈ Backup, Delete, Get, List, Purge, Recover, Restore, Set
}

variable "certificate_permissions" {
  type    = list(string)
  default = []
  # each element ∈ Backup, Create, Delete, DeleteIssuers, Get, GetIssuers, Import,
  #   List, ListIssuers, ManageContacts, ManageIssuers, Purge, Recover, Restore,
  #   SetIssuers, Update
}

variable "storage_permissions" {
  type    = list(string)
  default = []
  # each element ∈ Backup, Delete, DeleteSAS, Get, GetSAS, List, Purge, Recover,
  #   RegenerateKey, Restore, Set, SetSAS, Update
  # plus a cross-list check: total permissions across all four lists must be > 0
}

variable "timeouts" {
  type = object({
    create = optional(string)
    read   = optional(string)
    update = optional(string)
    delete = optional(string)
  })
  default = null
}

🧾 Outputs

Output Description Kind
id Passthrough
key_vault_id Resource ID of the vault this policy is attached to - a REAL Azure Resource ID, unlike this resource's own id Passthrough
tenant_id The Entra ID tenant the policy authenticates against Passthrough
object_id Object ID of the principal granted access Passthrough
application_id Application (client) object ID for the compound-identity case, or null Passthrough
key_permissions Key permissions granted, as stored Passthrough
secret_permissions Secret permissions granted, as stored Passthrough
certificate_permissions Certificate permissions granted, as stored Passthrough
storage_permissions Managed-storage permissions granted, as stored Passthrough
permission_count Total number of permissions granted across all four families Passthrough
grants_no_access True when all four permission lists are empty Passthrough
grants_purge_permission True when Purge appears in ANY of the four permission families Passthrough
grants_privileged_key_permissions True when the policy grants Purge or Release on keys - the two the provider classes as privileged Passthrough
grants_secret_read True when the policy grants Get on secrets - the permission that reads secret VALUES rather than merely enumerating names Passthrough
grants_storage_sas_issuance True when the policy grants GetSAS or SetSAS on managed storage Passthrough
id_is_not_an_azure_resource_id Always true, and the fact most likely to be misused about this module Constant
cannot_be_combined_with_the_vaults_inline_access_policy_block Always true Constant
is_inert_if_the_vault_uses_rbac_authorization Always true, and unverifiable from here Constant
applies_are_serialised_per_vault Always true Constant
every_argument_is_force_new Always true Constant

🧠 Architecture Notes

  • Force-new identity fields. key_vault_id, tenant_id, object_id, and application_id are immutable. Changing any of them replaces the policy. Treat a principal move or a vault move as a destroy-and-recreate, not an in-place edit.
  • Access-policy model, not RBAC. This module is only valid against a vault where enable_rbac_authorization = false. An RBAC-authorized vault has no access-policy surface and rejects the resource. Choose the vault's authorization model deliberately and stick with it.
  • object_id uniqueness. The provider treats one access policy per principal per vault. Declaring two policies for the same object_id on the same vault conflicts — model each principal exactly once, which a for_each map keyed by role enforces naturally.
  • Do not mix with inline blocks. If the azurerm_key_vault resource also declares inline access_policy blocks, Terraform's two views of the vault's policy set diverge and every plan shows a diff. This module assumes it is the sole owner of access policies on the vault.
  • Least privilege by construction. All four permission lists default to empty, so an empty module call grants nothing. Each element is validated against its closed, case-sensitive permission enum, and each list is checked for duplicates. An all-empty policy is accepted — the provider publishes no at-least-one rule, so refusing it would reject legal input and block terraform destroy; it is reported through grants_no_access instead.
  • features {} dependence. Like every module in this suite, this one declares no provider. Initialization in isolation requires the caller's provider "azurerm" { features {} } block.

🧱 Design Principles

Principle Secure default (empty call) Opt-out (caller must type it)
Grant nothing by default All four permission lists default to [] Populate the relevant list(s)
Reject a no-op policy At-least-one-permission cross-list check at parse time — (cannot be disabled)
Reject invalid verbs Each list element validated against its closed enum at parse time — (cannot be disabled)
No secret material Module carries only permission metadata; never accepts or emits a secret —
No tags Resource does not support tags; universal tail is timeouts only —

🚀 Runbook

terraform init -backend=false
terraform validate
terraform fmt -check

Pin the module with ?ref=v1.0.0 — never a branch. This library is plan-only: a human applies from CI. Nothing here performs a cloud apply.

🧪 Testing

The offline proof gate exercises everything that does not require Azure credentials:

  • terraform init -backend=false — resolves the provider without a backend.
  • terraform validate — confirms the type schema, the four permission enums, and the at-least-one cross-list check all parse. A malformed permission string or an all-empty call fails here, before any API call.
  • terraform fmt -check — enforces canonical formatting.

What only terraform plan (with credentials) exercises: whether the target vault actually uses the access-policy model, whether the object_id already has a policy on the vault, and whether the caller's identity holds the required Key Vault write permission.

💬 Example Output

$ terraform output
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-vault-demo/providers/Microsoft.KeyVault/vaults/kv-demo-eus2/objectId/11111111-1111-1111-1111-111111111111"

ℹ️ The id embeds the vault and the principal's object ID. It is Terraform's handle for the policy record, not an independently addressable Azure resource.

🔍 Troubleshooting

Symptom Cause Fix
Vault ... is configured for RBAC authorization; access policies are not supported The target vault uses the RBAC model. Recreate the vault with enable_rbac_authorization = false, or use a role-assignment module instead of this one.
An access policy already exists for object ID ... Two policies target the same object_id on one vault. Model the principal once; consolidate its permissions into a single module call.
Every plan shows a diff on the access policy Inline access_policy blocks on the vault fight this module for ownership. Remove the inline blocks and let this module own all access policies on the vault.
Each secret_permissions value must be one of: ... at plan time A permission string is misspelled or not in the enum. Correct the verb to a valid value listed in the input schema.
The principal still cannot reach the vault after a clean apply Any of three causes, none of which raises an error: the vault uses RBAC authorization, so policies are not consulted; an application ID was passed where the principal's object ID belongs (both are GUIDs, so every check passes); or all four permission lists are empty. Check is_inert_if_the_vault_uses_rbac_authorization, confirm the object ID is the principal's own, and check grants_no_access.
Each key_permissions value must be one of: ... at plan time A permission is misspelled, or lower-cased — the provider's check is case-sensitive. Use the exact capitalisation from the input schema; get is not Get.
Using id as an RBAC scope or a lock target fails This resource's id is a Terraform-only identifier and maps to no object in Azure. Use the key_vault_id output instead; see id_is_not_an_azure_resource_id.

🔗 Related Docs

💙 "Infrastructure as Code should be standardized, consistent, and secure."


Back | FazBrowse Home | New Git URL