Mini Shell
import salt.utils.json
import salt.utils.vault.leases as vleases
from salt.utils.vault.exceptions import VaultInvocationError, VaultNotFoundError
class AppRoleApi:
"""
Wraps the Vault AppRole API.
.. note::
All durations can be specified either as an integer time in seconds
or a time string like ``1h``.
https://developer.hashicorp.com/vault/api-docs/auth/approle
"""
def __init__(self, client):
self.client = client
def list_approles(self, mount="approle"):
"""
List all AppRoles present on the specified mount.
mount
Name of the AppRole auth backend mount.
Defaults to ``approle``.
"""
endpoint = f"auth/{mount}/role"
return self.client.list(endpoint)["data"]["keys"]
def read_approle(self, name, mount="approle"):
"""
Read the properties of an existing AppRole.
Raises VaultNotFound if the AppRole does not exist on the mount.
name
Name of the AppRole to read the properties of.
mount
Name of the AppRole auth backend mount.
Defaults to ``approle``.
"""
endpoint = f"auth/{mount}/role/{name}"
return self.client.get(endpoint)["data"]
def write_approle(
self,
name,
bind_secret_id=None,
secret_id_bound_cidrs=None,
secret_id_num_uses=None,
secret_id_ttl=None,
local_secret_ids=None,
token_ttl=None,
token_max_ttl=None,
token_policies=None,
token_bound_cidrs=None,
token_explicit_max_ttl=None,
token_no_default_policy=None,
token_num_uses=None,
token_period=None,
token_type=None,
mount="approle",
):
"""
Create or update an AppRole.
name
Name of the AppRole to read the properties of.
bind_secret_id
Require a SecretID when authenticating with this AppRole.
Defaults to true.
secret_id_bound_cidrs
List of blocks of IP addresses in CIDR notation that
can perform the login operation.
secret_id_num_uses
Number of times a generated SecretID can be used to authenticate
with this AppRole by default. ``0`` means unlimited.
secret_id_ttl
Duration after which a generated SecretID for this AppRole expires by default.
local_secret_ids
If set, the secret IDs generated using this role will be cluster-local.
This can only be set during role creation and once set, it can't be reset later.
Defaults to false.
token_ttl
The incremental lifetime for tokens generated by authenticating with this AppRole.
This value will be referenced at renewal time.
token_max_ttl
The maximum lifetime for tokens generated by authenticating with this AppRole.
This value will be referenced at renewal time.
token_policies
List of token policies to encode onto generated tokens.
This list may be supplemented by user/group/other values.
token_bound_cidrs
List of blocks of IP addresses in CIDR notation that
can perform the login operation. The resulting token will be tied
to these blocks as well.
token_explicit_max_ttl
Place a hard cap on the maximum lifetime of tokens issued by authenticating
with this AppRole.
token_no_default_policy
Do not add the ``default`` policy to tokens generated by authenticating
with this AppRole. Defaults to false.
token_num_uses
Number of times a token generated by authenticating with this AppRole
may be used to issue requests. ``0`` means unlimited.
token_period
The maximum allowed period value when a periodic token is requested from this role.
token_type
The type of token that should be generated (``service``, ``batch`` or ``default``).
mount
Name of the AppRole auth backend mount.
Defaults to ``approle``.
"""
endpoint = f"auth/{mount}/role/{name}"
payload = _filter_none(
{
"bind_secret_id": bind_secret_id,
"secret_id_bound_cidrs": secret_id_bound_cidrs,
"secret_id_num_uses": secret_id_num_uses,
"secret_id_ttl": secret_id_ttl,
"local_secret_ids": local_secret_ids,
"token_ttl": token_ttl,
"token_max_ttl": token_max_ttl,
"token_policies": token_policies,
"token_bound_cidrs": token_bound_cidrs,
"token_explicit_max_ttl": token_explicit_max_ttl,
"token_no_default_policy": token_no_default_policy,
"token_num_uses": token_num_uses,
"token_period": token_period,
"token_type": token_type,
}
)
return self.client.post(endpoint, payload=payload)
def delete_approle(self, name, mount="approle"):
"""
Delete an existing AppRole.
Raises VaultNotFound if the AppRole does not exist on the mount.
name
Name of the AppRole to delete.
mount
Name of the AppRole auth backend mount.
Defaults to ``approle``.
"""
endpoint = f"auth/{mount}/role/{name}"
return self.client.delete(endpoint)
def read_role_id(self, name, wrap=False, mount="approle"):
"""
Read the associated RoleID of an existing AppRole.
Raises VaultNotFound if the AppRole does not exist on the mount.
name
Name of the AppRole.
wrap
If set, specifies the duration the resulting wrapping token should
be valid for. This token can be used once to access the
query result. Defaults to false (=> returns the RoleID as a string).
mount
Name of the AppRole auth backend mount.
Defaults to ``approle``.
"""
endpoint = f"auth/{mount}/role/{name}/role-id"
role_id = self.client.get(endpoint, wrap=wrap)
if wrap:
return role_id
return role_id["data"]["role_id"]
def generate_secret_id(
self,
name,
metadata=None,
cidr_list=None,
token_bound_cidrs=None,
num_uses=None,
ttl=None,
wrap=False,
mount="approle",
):
"""
Generate a SecretID for an existing AppRole.
Raises VaultNotFound if the AppRole does not exist on the mount.
name
Name of the AppRole.
metadata
Mapping of string keys to string values that specifies metadata
to be set on the token generated by authenticating with this
specific SecretID. It will be logged to audit logs in plaintext.
cidr_list
List of blocks of IP addresses in CIDR notation that
can perform the login operation with this specific SecretID.
If ``secret_id_bound_cidrs`` is set on the AppRole, this list
must be a subset of the ones specified there.
token_bound_cidrs
List of blocks of IP addresses in CIDR notation that
can perform the login operation. The resulting token will be tied
to these blocks as well.
If ``token_bound_cidrs`` is set on the AppRole, this list
must be a subset of the ones specified there.
num_uses
Number of times this specific SecretID can be used to authenticate
by default. ``0`` means unlimited.
Must be equal to or lower than ``secret_id_num_uses`` set on the AppRole.
ttl
Duration after which this SecretID should expire.
Must be equal to or lower than ``secret_id_ttl`` set on the AppRole.
wrap
If set, specifies the duration the resulting wrapping token should
be valid for. This token can be used once to access the
query result. Defaults to false (=> returns the SecretID as a string).
mount
Name of the AppRole auth backend mount.
Defaults to ``approle``.
"""
endpoint = f"auth/{mount}/role/{name}/secret-id"
if metadata is not None:
metadata = salt.utils.json.dumps(metadata)
payload = _filter_none(
{
"metadata": metadata,
"cidr_list": cidr_list,
"token_bound_cidrs": token_bound_cidrs,
"num_uses": num_uses,
"ttl": ttl,
}
)
response = self.client.post(endpoint, payload=payload, wrap=wrap)
if wrap:
return response
# Sadly, secret_id_num_uses is not part of the information returned, but
# it can be read with `read_secret_id` using the accessor.
return vleases.VaultSecretId(**response["data"])
def read_secret_id(self, name, secret_id=None, accessor=None, mount="approle"):
"""
Read properties of an existing SecretID.
Raises VaultNotFound if the AppRole and/or SecretID does not exist on the mount.
name
Name of the AppRole the SecretID belongs to.
secret_id
The SecretID to look up. Specify either this or ``accessor``.
accessor
The accessor of the SecretID to look up. Specify either this
or ``secret_id``.
mount
Name of the AppRole auth backend mount.
Defaults to ``approle``.
"""
if not secret_id and not accessor:
raise VaultInvocationError(
"Need either secret_id or accessor to read secret ID."
)
if secret_id:
endpoint = f"auth/{mount}/role/{name}/secret-id/lookup"
payload = {"secret_id": str(secret_id)}
else:
endpoint = f"auth/{mount}/role/{name}/secret-id-accessor/lookup"
payload = {"secret_id_accessor": accessor}
try:
return self.client.post(endpoint, payload=payload)["data"]
except TypeError:
# lookup does not raise exceptions, only returns True
raise VaultNotFoundError()
def destroy_secret_id(self, name, secret_id=None, accessor=None, mount="approle"):
"""
Destroy an existing SecretID.
Raises VaultNotFound if the AppRole and/or SecretID does not exist on the mount.
name
Name of the AppRole the SecretID belongs to.
secret_id
The SecretID to destroy. Specify either this or ``accessor``.
accessor
The accessor of the SecretID to destroy. Specify either this
or ``secret_id``.
mount
Name of the AppRole auth backend mount.
Defaults to ``approle``.
"""
if not secret_id and not accessor:
raise VaultInvocationError(
"Need either secret_id or accessor to destroy secret ID."
)
if secret_id:
endpoint = f"auth/{mount}/role/{name}/secret-id/destroy"
payload = {"secret_id": str(secret_id)}
else:
endpoint = f"auth/{mount}/role/{name}/secret-id-accessor/destroy"
payload = {"secret_id_accessor": accessor}
return self.client.post(endpoint, payload=payload)
class IdentityApi:
"""
Wraps the Vault ``Identity`` secret engine API.
https://developer.hashicorp.com/vault/api-docs/secret/identity
"""
def __init__(self, client):
self.client = client
def list_entities(self):
"""
Return a list of the names of all entities known by Vault.
"""
endpoint = "identity/entity/name"
return self.client.list(endpoint)["data"]["keys"]
def read_entity(self, name):
"""
Read the properties of an entity by its name.
Raises VaultNotFound if the entity does not exist.
name
Name of the entity to read the properties of.
"""
endpoint = f"identity/entity/name/{name}"
return self.client.get(endpoint)["data"]
def read_entity_by_alias(self, alias, mount):
"""
Lookup the properties of an entity by its alias name and mount.
Raises VaultNotFound if the entity does not exist.
alias
The name of the entity's alias on the specified
``mount``. For AppRole backends, this is the RoleID.
mount
The name of the mount the given alias is associated with.
For example, if the backend is mounted at ``auth/approle``,
this should be ``approle``.
"""
endpoint = "identity/lookup/entity"
payload = {
"alias_name": alias,
"alias_mount_accessor": self._lookup_mount_accessor(mount),
}
entity = self.client.post(endpoint, payload=payload)
if isinstance(entity, dict):
return entity["data"]
raise VaultNotFoundError()
def write_entity(self, name, metadata=None, policies=None, disabled=None):
"""
Create or update an entity by name.
name
The name of the entity.
metadata
Mapping of string keys to string values that specifies metadata
to be set on the entity. This can be used to template policies.
policies
List of policies to be tied to the entity. These policies will
be active in addition to auth method-specific policies.
disabled
Whether this entity should be disabled. Disabled entities' associated
tokens cannot be used, but are not revoked. Defaults to false.
"""
endpoint = f"identity/entity/name/{name}"
payload = _filter_none(
{
"metadata": metadata,
"policies": policies,
"disabled": disabled,
}
)
return self.client.post(endpoint, payload=payload)
def delete_entity(self, name):
"""
Delete an entity by name.
Raises VaultNotFound if the entity does not exist.
name
The name of the entity.
"""
endpoint = f"identity/entity/name/{name}"
return self.client.delete(endpoint)
def write_entity_alias(self, name, alias_name, mount, custom_metadata=None):
"""
Create/update the association between an entity and a specific
alias of an auth mount.
name
Name of the entity to associate with the alias.
alias_name
Name of the alias to associate with the entity.
The specifics are dependent on the type of the auth backend.
For AppRoles, this is the RoleID.
mount
The name of the mount the given alias is associated with.
For example, if the backend is mounted at ``auth/approle``,
this should be ``approle``.
custom_metadata
A map of arbitrary string to string valued user-provided
metadata meant to describe the alias.
"""
entity = self.read_entity(name)
mount_accessor = self._lookup_mount_accessor(mount)
payload = {
"canonical_id": entity["id"],
"mount_accessor": mount_accessor,
"name": alias_name,
}
if custom_metadata is not None:
payload["custom_metadata"] = custom_metadata
for alias in entity["aliases"]:
# Ensure an existing alias is updated
if alias["mount_accessor"] == mount_accessor:
payload["id"] = alias["id"]
break
return self.client.post("identity/entity-alias", payload=payload)
def _lookup_mount_accessor(self, mount):
endpoint = f"sys/auth/{mount}"
return self.client.get(endpoint)["data"]["accessor"]
def _filter_none(data):
return {k: v for k, v in data.items() if v is not None}
Zerion Mini Shell 1.0