Merge branch 'staging' into environment-role-creation
This commit is contained in:
commit
10014e696d
@ -3,7 +3,7 @@
|
||||
"files": "^.secrets.baseline$|^.*pgsslrootcert.yml$",
|
||||
"lines": null
|
||||
},
|
||||
"generated_at": "2020-02-04T21:00:49Z",
|
||||
"generated_at": "2020-02-10T21:40:38Z",
|
||||
"plugins_used": [
|
||||
{
|
||||
"base64_limit": 4.5,
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""change to environment_roles.cloud_Id
|
||||
|
||||
Revision ID: 418b52c1cedf
|
||||
Revises: 567bfb019a87
|
||||
Revises: 542bd3215dec
|
||||
Create Date: 2020-02-05 13:40:37.870183
|
||||
|
||||
"""
|
||||
@ -11,7 +11,7 @@ import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '418b52c1cedf' # pragma: allowlist secret
|
||||
down_revision = '567bfb019a87' # pragma: allowlist secret
|
||||
down_revision = '542bd3215dec' # pragma: allowlist secret
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
264
alembic/versions/542bd3215dec_state_machine_stage_added.py
Normal file
264
alembic/versions/542bd3215dec_state_machine_stage_added.py
Normal file
@ -0,0 +1,264 @@
|
||||
"""state machine stage added.
|
||||
|
||||
Revision ID: 542bd3215dec
|
||||
Revises: 567bfb019a87
|
||||
Create Date: 2020-02-06 12:01:58.077840
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "542bd3215dec" # pragma: allowlist secret
|
||||
down_revision = "567bfb019a87" # pragma: allowlist secret
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column(
|
||||
"portfolio_state_machines",
|
||||
"state",
|
||||
type_=sa.Enum(
|
||||
"UNSTARTED",
|
||||
"STARTING",
|
||||
"STARTED",
|
||||
"COMPLETED",
|
||||
"FAILED",
|
||||
"TENANT_CREATED",
|
||||
"TENANT_IN_PROGRESS",
|
||||
"TENANT_FAILED",
|
||||
"BILLING_PROFILE_CREATION_CREATED",
|
||||
"BILLING_PROFILE_CREATION_IN_PROGRESS",
|
||||
"BILLING_PROFILE_CREATION_FAILED",
|
||||
"BILLING_PROFILE_VERIFICATION_CREATED",
|
||||
"BILLING_PROFILE_VERIFICATION_IN_PROGRESS",
|
||||
"BILLING_PROFILE_VERIFICATION_FAILED",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_CREATED",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_FAILED",
|
||||
"TASK_ORDER_BILLING_CREATION_CREATED",
|
||||
"TASK_ORDER_BILLING_CREATION_IN_PROGRESS",
|
||||
"TASK_ORDER_BILLING_CREATION_FAILED",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_CREATED",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_FAILED",
|
||||
"BILLING_INSTRUCTION_CREATED",
|
||||
"BILLING_INSTRUCTION_IN_PROGRESS",
|
||||
"BILLING_INSTRUCTION_FAILED",
|
||||
"PRODUCT_PURCHASE_CREATED",
|
||||
"PRODUCT_PURCHASE_IN_PROGRESS",
|
||||
"PRODUCT_PURCHASE_FAILED",
|
||||
"PRODUCT_PURCHASE_VERIFICATION_CREATED",
|
||||
"PRODUCT_PURCHASE_VERIFICATION_IN_PROGRESS",
|
||||
"PRODUCT_PURCHASE_VERIFICATION_FAILED",
|
||||
"TENANT_PRINCIPAL_APP_CREATED",
|
||||
"TENANT_PRINCIPAL_APP_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_APP_FAILED",
|
||||
"TENANT_PRINCIPAL_CREATED",
|
||||
"TENANT_PRINCIPAL_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_FAILED",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_CREATED",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_FAILED",
|
||||
"ADMIN_ROLE_DEFINITION_CREATED",
|
||||
"ADMIN_ROLE_DEFINITION_IN_PROGRESS",
|
||||
"ADMIN_ROLE_DEFINITION_FAILED",
|
||||
"PRINCIPAL_ADMIN_ROLE_CREATED",
|
||||
"PRINCIPAL_ADMIN_ROLE_IN_PROGRESS",
|
||||
"PRINCIPAL_ADMIN_ROLE_FAILED",
|
||||
"INITIAL_MGMT_GROUP_CREATED",
|
||||
"INITIAL_MGMT_GROUP_IN_PROGRESS",
|
||||
"INITIAL_MGMT_GROUP_FAILED",
|
||||
"INITIAL_MGMT_GROUP_VERIFICATION_CREATED",
|
||||
"INITIAL_MGMT_GROUP_VERIFICATION_IN_PROGRESS",
|
||||
"INITIAL_MGMT_GROUP_VERIFICATION_FAILED",
|
||||
"TENANT_ADMIN_OWNERSHIP_CREATED",
|
||||
"TENANT_ADMIN_OWNERSHIP_IN_PROGRESS",
|
||||
"TENANT_ADMIN_OWNERSHIP_FAILED",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_CREATED",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_FAILED",
|
||||
name="fsmstates",
|
||||
native_enum=False,
|
||||
),
|
||||
existing_type=sa.Enum(
|
||||
"UNSTARTED",
|
||||
"STARTING",
|
||||
"STARTED",
|
||||
"COMPLETED",
|
||||
"FAILED",
|
||||
"TENANT_CREATED",
|
||||
"TENANT_IN_PROGRESS",
|
||||
"TENANT_FAILED",
|
||||
"BILLING_PROFILE_CREATION_CREATED",
|
||||
"BILLING_PROFILE_CREATION_IN_PROGRESS",
|
||||
"BILLING_PROFILE_CREATION_FAILED",
|
||||
"BILLING_PROFILE_VERIFICATION_CREATED",
|
||||
"BILLING_PROFILE_VERIFICATION_IN_PROGRESS",
|
||||
"BILLING_PROFILE_VERIFICATION_FAILED",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_CREATED",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_FAILED",
|
||||
"TASK_ORDER_BILLING_CREATION_CREATED",
|
||||
"TASK_ORDER_BILLING_CREATION_IN_PROGRESS",
|
||||
"TASK_ORDER_BILLING_CREATION_FAILED",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_CREATED",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_FAILED",
|
||||
"BILLING_INSTRUCTION_CREATED",
|
||||
"BILLING_INSTRUCTION_IN_PROGRESS",
|
||||
"BILLING_INSTRUCTION_FAILED",
|
||||
"TENANT_PRINCIPAL_APP_CREATED",
|
||||
"TENANT_PRINCIPAL_APP_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_APP_FAILED",
|
||||
"TENANT_PRINCIPAL_CREATED",
|
||||
"TENANT_PRINCIPAL_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_FAILED",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_CREATED",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_FAILED",
|
||||
"ADMIN_ROLE_DEFINITION_CREATED",
|
||||
"ADMIN_ROLE_DEFINITION_IN_PROGRESS",
|
||||
"ADMIN_ROLE_DEFINITION_FAILED",
|
||||
"PRINCIPAL_ADMIN_ROLE_CREATED",
|
||||
"PRINCIPAL_ADMIN_ROLE_IN_PROGRESS",
|
||||
"PRINCIPAL_ADMIN_ROLE_FAILED",
|
||||
"TENANT_ADMIN_OWNERSHIP_CREATED",
|
||||
"TENANT_ADMIN_OWNERSHIP_IN_PROGRESS",
|
||||
"TENANT_ADMIN_OWNERSHIP_FAILED",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_CREATED",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_FAILED",
|
||||
name="fsmstates",
|
||||
native_enum=False,
|
||||
),
|
||||
existing_nullable=False,
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column(
|
||||
"portfolio_state_machines",
|
||||
"state",
|
||||
type_=sa.Enum(
|
||||
"UNSTARTED",
|
||||
"STARTING",
|
||||
"STARTED",
|
||||
"COMPLETED",
|
||||
"FAILED",
|
||||
"TENANT_CREATED",
|
||||
"TENANT_IN_PROGRESS",
|
||||
"TENANT_FAILED",
|
||||
"BILLING_PROFILE_CREATION_CREATED",
|
||||
"BILLING_PROFILE_CREATION_IN_PROGRESS",
|
||||
"BILLING_PROFILE_CREATION_FAILED",
|
||||
"BILLING_PROFILE_VERIFICATION_CREATED",
|
||||
"BILLING_PROFILE_VERIFICATION_IN_PROGRESS",
|
||||
"BILLING_PROFILE_VERIFICATION_FAILED",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_CREATED",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_FAILED",
|
||||
"TASK_ORDER_BILLING_CREATION_CREATED",
|
||||
"TASK_ORDER_BILLING_CREATION_IN_PROGRESS",
|
||||
"TASK_ORDER_BILLING_CREATION_FAILED",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_CREATED",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_FAILED",
|
||||
"BILLING_INSTRUCTION_CREATED",
|
||||
"BILLING_INSTRUCTION_IN_PROGRESS",
|
||||
"BILLING_INSTRUCTION_FAILED",
|
||||
"TENANT_PRINCIPAL_APP_CREATED",
|
||||
"TENANT_PRINCIPAL_APP_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_APP_FAILED",
|
||||
"TENANT_PRINCIPAL_CREATED",
|
||||
"TENANT_PRINCIPAL_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_FAILED",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_CREATED",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_FAILED",
|
||||
"ADMIN_ROLE_DEFINITION_CREATED",
|
||||
"ADMIN_ROLE_DEFINITION_IN_PROGRESS",
|
||||
"ADMIN_ROLE_DEFINITION_FAILED",
|
||||
"PRINCIPAL_ADMIN_ROLE_CREATED",
|
||||
"PRINCIPAL_ADMIN_ROLE_IN_PROGRESS",
|
||||
"PRINCIPAL_ADMIN_ROLE_FAILED",
|
||||
"TENANT_ADMIN_OWNERSHIP_CREATED",
|
||||
"TENANT_ADMIN_OWNERSHIP_IN_PROGRESS",
|
||||
"TENANT_ADMIN_OWNERSHIP_FAILED",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_CREATED",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_FAILED",
|
||||
name="fsmstates",
|
||||
native_enum=False,
|
||||
),
|
||||
existing_type=sa.Enum(
|
||||
"UNSTARTED",
|
||||
"STARTING",
|
||||
"STARTED",
|
||||
"COMPLETED",
|
||||
"FAILED",
|
||||
"TENANT_CREATED",
|
||||
"TENANT_IN_PROGRESS",
|
||||
"TENANT_FAILED",
|
||||
"BILLING_PROFILE_CREATION_CREATED",
|
||||
"BILLING_PROFILE_CREATION_IN_PROGRESS",
|
||||
"BILLING_PROFILE_CREATION_FAILED",
|
||||
"BILLING_PROFILE_VERIFICATION_CREATED",
|
||||
"BILLING_PROFILE_VERIFICATION_IN_PROGRESS",
|
||||
"BILLING_PROFILE_VERIFICATION_FAILED",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_CREATED",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS",
|
||||
"BILLING_PROFILE_TENANT_ACCESS_FAILED",
|
||||
"TASK_ORDER_BILLING_CREATION_CREATED",
|
||||
"TASK_ORDER_BILLING_CREATION_IN_PROGRESS",
|
||||
"TASK_ORDER_BILLING_CREATION_FAILED",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_CREATED",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS",
|
||||
"TASK_ORDER_BILLING_VERIFICATION_FAILED",
|
||||
"BILLING_INSTRUCTION_CREATED",
|
||||
"BILLING_INSTRUCTION_IN_PROGRESS",
|
||||
"BILLING_INSTRUCTION_FAILED",
|
||||
"PRODUCT_PURCHASE_CREATED",
|
||||
"PRODUCT_PURCHASE_IN_PROGRESS",
|
||||
"PRODUCT_PURCHASE_FAILED",
|
||||
"PRODUCT_PURCHASE_VERIFICATION_CREATED",
|
||||
"PRODUCT_PURCHASE_VERIFICATION_IN_PROGRESS",
|
||||
"PRODUCT_PURCHASE_VERIFICATION_FAILED",
|
||||
"TENANT_PRINCIPAL_APP_CREATED",
|
||||
"TENANT_PRINCIPAL_APP_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_APP_FAILED",
|
||||
"TENANT_PRINCIPAL_CREATED",
|
||||
"TENANT_PRINCIPAL_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_FAILED",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_CREATED",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_CREDENTIAL_FAILED",
|
||||
"ADMIN_ROLE_DEFINITION_CREATED",
|
||||
"ADMIN_ROLE_DEFINITION_IN_PROGRESS",
|
||||
"ADMIN_ROLE_DEFINITION_FAILED",
|
||||
"PRINCIPAL_ADMIN_ROLE_CREATED",
|
||||
"PRINCIPAL_ADMIN_ROLE_IN_PROGRESS",
|
||||
"PRINCIPAL_ADMIN_ROLE_FAILED",
|
||||
"INITIAL_MGMT_GROUP_CREATED",
|
||||
"INITIAL_MGMT_GROUP_IN_PROGRESS",
|
||||
"INITIAL_MGMT_GROUP_FAILED",
|
||||
"INITIAL_MGMT_GROUP_VERIFICATION_CREATED",
|
||||
"INITIAL_MGMT_GROUP_VERIFICATION_IN_PROGRESS",
|
||||
"INITIAL_MGMT_GROUP_VERIFICATION_FAILED",
|
||||
"TENANT_ADMIN_OWNERSHIP_CREATED",
|
||||
"TENANT_ADMIN_OWNERSHIP_IN_PROGRESS",
|
||||
"TENANT_ADMIN_OWNERSHIP_FAILED",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_CREATED",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS",
|
||||
"TENANT_PRINCIPAL_OWNERSHIP_FAILED",
|
||||
name="fsmstates",
|
||||
native_enum=False,
|
||||
),
|
||||
existing_nullable=False,
|
||||
)
|
||||
|
24
atst/app.py
24
atst/app.py
@ -157,7 +157,6 @@ def map_config(config):
|
||||
**config["default"],
|
||||
"USE_AUDIT_LOG": config["default"].getboolean("USE_AUDIT_LOG"),
|
||||
"ENV": config["default"]["ENVIRONMENT"],
|
||||
"BROKER_URL": config["default"]["REDIS_URI"],
|
||||
"DEBUG": config["default"].getboolean("DEBUG"),
|
||||
"DEBUG_MAILER": config["default"].getboolean("DEBUG_MAILER"),
|
||||
"SQLALCHEMY_ECHO": config["default"].getboolean("SQLALCHEMY_ECHO"),
|
||||
@ -233,13 +232,34 @@ def make_config(direct_config=None):
|
||||
config.set("default", "DATABASE_URI", database_uri)
|
||||
|
||||
# Assemble REDIS_URI value
|
||||
redis_use_tls = config["default"].getboolean("REDIS_TLS")
|
||||
redis_uri = "redis{}://{}:{}@{}".format( # pragma: allowlist secret
|
||||
("s" if config["default"].getboolean("REDIS_TLS") else ""),
|
||||
("s" if redis_use_tls else ""),
|
||||
(config.get("default", "REDIS_USER") or ""),
|
||||
(config.get("default", "REDIS_PASSWORD") or ""),
|
||||
config.get("default", "REDIS_HOST"),
|
||||
)
|
||||
celery_uri = redis_uri
|
||||
if redis_use_tls:
|
||||
tls_mode = config.get("default", "REDIS_SSLMODE")
|
||||
tls_mode_str = tls_mode.lower() if tls_mode else "none"
|
||||
redis_uri = f"{redis_uri}/?ssl_cert_reqs={tls_mode_str}"
|
||||
|
||||
# TODO: Kombu, one of Celery's dependencies, still requires
|
||||
# that ssl_cert_reqs be passed as the string version of an
|
||||
# option on the ssl module. We can clean this up and use
|
||||
# the REDIS_URI for both when this PR to Kombu is released:
|
||||
# https://github.com/celery/kombu/pull/1139
|
||||
kombu_modes = {
|
||||
"none": "CERT_NONE",
|
||||
"required": "CERT_REQUIRED",
|
||||
"optional": "CERT_OPTIONAL",
|
||||
}
|
||||
celery_tls_mode_str = kombu_modes[tls_mode_str]
|
||||
celery_uri = f"{celery_uri}/?ssl_cert_reqs={celery_tls_mode_str}"
|
||||
|
||||
config.set("default", "REDIS_URI", redis_uri)
|
||||
config.set("default", "BROKER_URL", celery_uri)
|
||||
|
||||
return map_config(config)
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import json
|
||||
from secrets import token_urlsafe
|
||||
from typing import Any, Dict
|
||||
from uuid import uuid4
|
||||
|
||||
from atst.utils import sha256_hex
|
||||
@ -25,6 +24,10 @@ from .models import (
|
||||
BillingProfileVerificationCSPPayload,
|
||||
BillingProfileVerificationCSPResult,
|
||||
CostManagementQueryCSPResult,
|
||||
InitialMgmtGroupCSPPayload,
|
||||
InitialMgmtGroupCSPResult,
|
||||
InitialMgmtGroupVerificationCSPPayload,
|
||||
InitialMgmtGroupVerificationCSPResult,
|
||||
EnvironmentCSPPayload,
|
||||
EnvironmentCSPResult,
|
||||
KeyVaultCredentials,
|
||||
@ -195,6 +198,38 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
|
||||
return ApplicationCSPResult(**response)
|
||||
|
||||
def create_initial_mgmt_group(self, payload: InitialMgmtGroupCSPPayload):
|
||||
creds = self._source_creds(payload.tenant_id)
|
||||
credentials = self._get_credential_obj(
|
||||
{
|
||||
"client_id": creds.root_sp_client_id,
|
||||
"secret_key": creds.root_sp_key,
|
||||
"tenant_id": creds.root_tenant_id,
|
||||
},
|
||||
resource=self.sdk.cloud.endpoints.resource_manager,
|
||||
)
|
||||
response = self._create_management_group(
|
||||
credentials, payload.management_group_name, payload.display_name,
|
||||
)
|
||||
|
||||
return InitialMgmtGroupCSPResult(**response)
|
||||
|
||||
def create_initial_mgmt_group_verification(
|
||||
self, payload: InitialMgmtGroupVerificationCSPPayload
|
||||
):
|
||||
creds = self._source_creds(payload.tenant_id)
|
||||
credentials = self._get_credential_obj(
|
||||
{
|
||||
"client_id": creds.root_sp_client_id,
|
||||
"secret_key": creds.root_sp_key,
|
||||
"tenant_id": creds.root_tenant_id,
|
||||
},
|
||||
resource=self.sdk.cloud.endpoints.resource_manager,
|
||||
)
|
||||
|
||||
response = self._get_management_group(credentials, payload.tenant_id,)
|
||||
return InitialMgmtGroupVerificationCSPResult(**response.result())
|
||||
|
||||
def _create_management_group(
|
||||
self, credentials, management_group_id, display_name, parent_id=None,
|
||||
):
|
||||
@ -221,6 +256,11 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
# instead?
|
||||
return create_request.result()
|
||||
|
||||
def _get_management_group(self, credentials, management_group_id):
|
||||
mgmgt_group_client = self.sdk.managementgroups.ManagementGroupsAPI(credentials)
|
||||
response = mgmgt_group_client.management_groups.get(management_group_id)
|
||||
return response
|
||||
|
||||
def _create_policy_definition(
|
||||
self, credentials, subscription_id, management_group_id, properties,
|
||||
):
|
||||
@ -1066,12 +1106,10 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
|
||||
def update_tenant_creds(self, tenant_id, secret: KeyVaultCredentials):
|
||||
hashed = sha256_hex(tenant_id)
|
||||
new_secrets = secret.dict()
|
||||
curr_secrets = self._source_tenant_creds(tenant_id)
|
||||
updated_secrets: Dict[str, Any] = {**curr_secrets.dict(), **new_secrets}
|
||||
us = KeyVaultCredentials(**updated_secrets)
|
||||
self.set_secret(hashed, json.dumps(us.dict()))
|
||||
return us
|
||||
updated_secrets = curr_secrets.merge_credentials(secret)
|
||||
self.set_secret(hashed, json.dumps(updated_secrets.dict()))
|
||||
return updated_secrets
|
||||
|
||||
def _source_tenant_creds(self, tenant_id) -> KeyVaultCredentials:
|
||||
hashed = sha256_hex(tenant_id)
|
||||
@ -1100,7 +1138,7 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
"timeframe": "Custom",
|
||||
"timePeriod": {"from": payload.from_date, "to": payload.to_date,},
|
||||
"dataset": {
|
||||
"granularity": "Daily",
|
||||
"granularity": "Monthly",
|
||||
"aggregation": {"totalCost": {"name": "PreTaxCost", "function": "Sum"}},
|
||||
"grouping": [{"type": "Dimension", "name": "InvoiceId"}],
|
||||
},
|
||||
|
@ -1,4 +1,5 @@
|
||||
from uuid import uuid4
|
||||
import pendulum
|
||||
|
||||
from .cloud_provider_interface import CloudProviderInterface
|
||||
from .exceptions import (
|
||||
@ -23,6 +24,10 @@ from .models import (
|
||||
BillingProfileTenantAccessCSPResult,
|
||||
BillingProfileVerificationCSPPayload,
|
||||
BillingProfileVerificationCSPResult,
|
||||
InitialMgmtGroupCSPPayload,
|
||||
InitialMgmtGroupCSPResult,
|
||||
InitialMgmtGroupVerificationCSPPayload,
|
||||
InitialMgmtGroupVerificationCSPResult,
|
||||
CostManagementQueryCSPResult,
|
||||
CostManagementQueryProperties,
|
||||
ProductPurchaseCSPPayload,
|
||||
@ -280,6 +285,29 @@ class MockCloudProvider(CloudProviderInterface):
|
||||
}
|
||||
)
|
||||
|
||||
def create_initial_mgmt_group(self, payload: InitialMgmtGroupCSPPayload):
|
||||
self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION)
|
||||
self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION)
|
||||
self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION)
|
||||
|
||||
return InitialMgmtGroupCSPResult(
|
||||
id=f"{AZURE_MGMNT_PATH}{payload.management_group_name}",
|
||||
)
|
||||
|
||||
def create_initial_mgmt_group_verification(
|
||||
self, payload: InitialMgmtGroupVerificationCSPPayload
|
||||
):
|
||||
self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION)
|
||||
self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION)
|
||||
self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION)
|
||||
|
||||
return InitialMgmtGroupVerificationCSPResult(
|
||||
**dict(
|
||||
id="Test Id"
|
||||
# id=f"{AZURE_MGMNT_PATH}{payload.management_group_name}"
|
||||
)
|
||||
)
|
||||
|
||||
def create_product_purchase(self, payload: ProductPurchaseCSPPayload):
|
||||
self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION)
|
||||
self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION)
|
||||
@ -459,15 +487,26 @@ class MockCloudProvider(CloudProviderInterface):
|
||||
self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION)
|
||||
object_id = str(uuid4())
|
||||
|
||||
start_of_month = pendulum.today(tz="utc").start_of("month").replace(tzinfo=None)
|
||||
this_month = start_of_month.to_atom_string()
|
||||
last_month = start_of_month.subtract(months=1).to_atom_string()
|
||||
two_months_ago = start_of_month.subtract(months=2).to_atom_string()
|
||||
|
||||
properties = CostManagementQueryProperties(
|
||||
**dict(
|
||||
columns=[
|
||||
{"name": "PreTaxCost", "type": "Number"},
|
||||
{"name": "UsageDate", "type": "Number"},
|
||||
{"name": "BillingMonth", "type": "Datetime"},
|
||||
{"name": "InvoiceId", "type": "String"},
|
||||
{"name": "Currency", "type": "String"},
|
||||
],
|
||||
rows=[],
|
||||
rows=[
|
||||
[1.0, two_months_ago, "", "USD"],
|
||||
[500.0, two_months_ago, "e05009w9sf", "USD"],
|
||||
[50.0, last_month, "", "USD"],
|
||||
[1000.0, last_month, "e0500a4qhw", "USD"],
|
||||
[500.0, this_month, "", "USD"],
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -321,7 +321,7 @@ class ManagementGroupCSPPayload(AliasModel):
|
||||
tenant_id: str
|
||||
management_group_name: Optional[str]
|
||||
display_name: str
|
||||
parent_id: str
|
||||
parent_id: Optional[str]
|
||||
|
||||
@validator("management_group_name", pre=True, always=True)
|
||||
def supply_management_group_name_default(cls, name):
|
||||
@ -341,16 +341,25 @@ class ManagementGroupCSPPayload(AliasModel):
|
||||
|
||||
@validator("parent_id", pre=True, always=True)
|
||||
def enforce_parent_id_pattern(cls, id_):
|
||||
if AZURE_MGMNT_PATH not in id_:
|
||||
return f"{AZURE_MGMNT_PATH}{id_}"
|
||||
else:
|
||||
return id_
|
||||
if id_:
|
||||
if AZURE_MGMNT_PATH not in id_:
|
||||
return f"{AZURE_MGMNT_PATH}{id_}"
|
||||
else:
|
||||
return id_
|
||||
|
||||
|
||||
class ManagementGroupCSPResponse(AliasModel):
|
||||
id: str
|
||||
|
||||
|
||||
class ManagementGroupGetCSPPayload(BaseCSPPayload):
|
||||
management_group_name: str
|
||||
|
||||
|
||||
class ManagementGroupGetCSPResponse(AliasModel):
|
||||
id: str
|
||||
|
||||
|
||||
class ApplicationCSPPayload(ManagementGroupCSPPayload):
|
||||
pass
|
||||
|
||||
@ -359,6 +368,22 @@ class ApplicationCSPResult(ManagementGroupCSPResponse):
|
||||
pass
|
||||
|
||||
|
||||
class InitialMgmtGroupCSPPayload(ManagementGroupCSPPayload):
|
||||
pass
|
||||
|
||||
|
||||
class InitialMgmtGroupCSPResult(ManagementGroupCSPResponse):
|
||||
pass
|
||||
|
||||
|
||||
class InitialMgmtGroupVerificationCSPPayload(ManagementGroupGetCSPPayload):
|
||||
pass
|
||||
|
||||
|
||||
class InitialMgmtGroupVerificationCSPResult(ManagementGroupGetCSPResponse):
|
||||
pass
|
||||
|
||||
|
||||
class EnvironmentCSPPayload(ManagementGroupCSPPayload):
|
||||
pass
|
||||
|
||||
@ -418,6 +443,15 @@ class KeyVaultCredentials(BaseModel):
|
||||
|
||||
return values
|
||||
|
||||
def merge_credentials(
|
||||
self, new_creds: "KeyVaultCredentials"
|
||||
) -> "KeyVaultCredentials":
|
||||
updated_creds = {k: v for k, v in new_creds.dict().items() if v}
|
||||
old_creds = self.dict()
|
||||
old_creds.update(updated_creds)
|
||||
|
||||
return KeyVaultCredentials(**old_creds)
|
||||
|
||||
|
||||
class SubscriptionCreationCSPPayload(BaseCSPPayload):
|
||||
display_name: str
|
||||
|
@ -1,6 +1,6 @@
|
||||
from collections import defaultdict
|
||||
import json
|
||||
from decimal import Decimal
|
||||
import pendulum
|
||||
|
||||
|
||||
def load_fixture_data():
|
||||
@ -11,128 +11,25 @@ def load_fixture_data():
|
||||
class MockReportingProvider:
|
||||
FIXTURE_SPEND_DATA = load_fixture_data()
|
||||
|
||||
@classmethod
|
||||
def get_portfolio_monthly_spending(cls, portfolio):
|
||||
"""
|
||||
returns an array of application and environment spending for the
|
||||
portfolio. Applications and their nested environments are sorted in
|
||||
alphabetical order by name.
|
||||
[
|
||||
{
|
||||
name
|
||||
this_month
|
||||
last_month
|
||||
total
|
||||
environments [
|
||||
{
|
||||
name
|
||||
this_month
|
||||
last_month
|
||||
total
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
||||
fixture_apps = cls.FIXTURE_SPEND_DATA.get(portfolio.name, {}).get(
|
||||
"applications", []
|
||||
)
|
||||
def prepare_azure_reporting_data(rows: list):
|
||||
"""
|
||||
Returns a dict representing invoiced and estimated funds for a portfolio given
|
||||
a list of rows from CostManagementQueryCSPResult.properties.rows
|
||||
{
|
||||
invoiced: Decimal,
|
||||
estimated: Decimal
|
||||
}
|
||||
"""
|
||||
|
||||
for application in portfolio.applications:
|
||||
if application.name not in [app["name"] for app in fixture_apps]:
|
||||
fixture_apps.append({"name": application.name, "environments": []})
|
||||
estimated = []
|
||||
while rows:
|
||||
if pendulum.parse(rows[-1][1]) >= pendulum.now(tz="utc").start_of("month"):
|
||||
estimated.append(rows.pop())
|
||||
else:
|
||||
break
|
||||
|
||||
return sorted(
|
||||
[
|
||||
cls._get_application_monthly_totals(portfolio, fixture_app)
|
||||
for fixture_app in fixture_apps
|
||||
if fixture_app["name"]
|
||||
in [application.name for application in portfolio.applications]
|
||||
],
|
||||
key=lambda app: app["name"],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_environment_monthly_totals(cls, environment):
|
||||
"""
|
||||
returns a dictionary that represents spending totals for an environment e.g.
|
||||
{
|
||||
name
|
||||
this_month
|
||||
last_month
|
||||
total
|
||||
}
|
||||
"""
|
||||
return {
|
||||
"name": environment["name"],
|
||||
"this_month": sum(environment["spending"]["this_month"].values()),
|
||||
"last_month": sum(environment["spending"]["last_month"].values()),
|
||||
"total": sum(environment["spending"]["total"].values()),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _get_application_monthly_totals(cls, portfolio, fixture_app):
|
||||
"""
|
||||
returns a dictionary that represents spending totals for an application
|
||||
and its environments e.g.
|
||||
{
|
||||
name
|
||||
this_month
|
||||
last_month
|
||||
total
|
||||
environments: [
|
||||
{
|
||||
name
|
||||
this_month
|
||||
last_month
|
||||
total
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
application_envs = [
|
||||
env
|
||||
for env in portfolio.all_environments
|
||||
if env.application.name == fixture_app["name"]
|
||||
]
|
||||
|
||||
environments = [
|
||||
cls._get_environment_monthly_totals(env)
|
||||
for env in fixture_app["environments"]
|
||||
if env["name"] in [e.name for e in application_envs]
|
||||
]
|
||||
|
||||
for env in application_envs:
|
||||
if env.name not in [env["name"] for env in environments]:
|
||||
environments.append({"name": env.name})
|
||||
|
||||
return {
|
||||
"name": fixture_app["name"],
|
||||
"this_month": sum(env.get("this_month", 0) for env in environments),
|
||||
"last_month": sum(env.get("last_month", 0) for env in environments),
|
||||
"total": sum(env.get("total", 0) for env in environments),
|
||||
"environments": sorted(environments, key=lambda env: env["name"]),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_spending_by_JEDI_clin(cls, portfolio):
|
||||
"""
|
||||
returns an dictionary of spending per JEDI CLIN for a portfolio
|
||||
{
|
||||
jedi_clin: {
|
||||
invoiced
|
||||
estimated
|
||||
},
|
||||
}
|
||||
"""
|
||||
if portfolio.name in cls.FIXTURE_SPEND_DATA:
|
||||
CLIN_spend_dict = defaultdict(lambda: defaultdict(Decimal))
|
||||
for application in cls.FIXTURE_SPEND_DATA[portfolio.name]["applications"]:
|
||||
for environment in application["environments"]:
|
||||
for clin, spend in environment["spending"]["this_month"].items():
|
||||
CLIN_spend_dict[clin]["estimated"] += Decimal(spend)
|
||||
for clin, spend in environment["spending"]["total"].items():
|
||||
CLIN_spend_dict[clin]["invoiced"] += Decimal(spend)
|
||||
return CLIN_spend_dict
|
||||
return {}
|
||||
return dict(
|
||||
invoiced=Decimal(sum([row[0] for row in rows])),
|
||||
estimated=Decimal(sum([row[0] for row in estimated])),
|
||||
)
|
||||
|
@ -1,12 +1,13 @@
|
||||
from flask import current_app
|
||||
from itertools import groupby
|
||||
from atst.domain.csp.cloud.models import (
|
||||
ReportingCSPPayload,
|
||||
CostManagementQueryCSPResult,
|
||||
)
|
||||
from atst.domain.csp.reports import prepare_azure_reporting_data
|
||||
import pendulum
|
||||
|
||||
|
||||
class Reports:
|
||||
@classmethod
|
||||
def monthly_spending(cls, portfolio):
|
||||
return current_app.csp.reports.get_portfolio_monthly_spending(portfolio)
|
||||
|
||||
@classmethod
|
||||
def expired_task_orders(cls, portfolio):
|
||||
return [
|
||||
@ -14,31 +15,19 @@ class Reports:
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def obligated_funds_by_JEDI_clin(cls, portfolio):
|
||||
clin_spending = current_app.csp.reports.get_spending_by_JEDI_clin(portfolio)
|
||||
active_clins = portfolio.active_clins
|
||||
for jedi_clin, clins in groupby(
|
||||
active_clins, key=lambda clin: clin.jedi_clin_type
|
||||
):
|
||||
if not clin_spending.get(jedi_clin.name):
|
||||
clin_spending[jedi_clin.name] = {}
|
||||
clin_spending[jedi_clin.name]["obligated"] = sum(
|
||||
clin.obligated_amount for clin in clins
|
||||
)
|
||||
def get_portfolio_spending(cls, portfolio):
|
||||
# TODO: Extend this function to make from_date and to_date configurable
|
||||
from_date = pendulum.now().subtract(years=1).add(days=1).format("YYYY-MM-DD")
|
||||
to_date = pendulum.now().format("YYYY-MM-DD")
|
||||
rows = []
|
||||
|
||||
output = []
|
||||
for clin in clin_spending.keys():
|
||||
invoiced = clin_spending[clin].get("invoiced", 0)
|
||||
estimated = clin_spending[clin].get("estimated", 0)
|
||||
obligated = clin_spending[clin].get("obligated", 0)
|
||||
remaining = obligated - (invoiced + estimated)
|
||||
output.append(
|
||||
{
|
||||
"name": clin,
|
||||
"invoiced": invoiced,
|
||||
"estimated": estimated,
|
||||
"obligated": obligated,
|
||||
"remaining": remaining,
|
||||
}
|
||||
if portfolio.csp_data:
|
||||
payload = ReportingCSPPayload(
|
||||
from_date=from_date, to_date=to_date, **portfolio.csp_data
|
||||
)
|
||||
return output
|
||||
response: CostManagementQueryCSPResult = current_app.csp.cloud.get_reporting_data(
|
||||
payload
|
||||
)
|
||||
rows = response.properties.rows
|
||||
|
||||
return prepare_azure_reporting_data(rows)
|
||||
|
@ -5,7 +5,7 @@ from flask import render_template
|
||||
from jinja2 import contextfilter
|
||||
from jinja2.exceptions import TemplateNotFound
|
||||
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode
|
||||
from decimal import DivisionByZero as DivisionByZeroException
|
||||
from decimal import DivisionByZero as DivisionByZeroException, InvalidOperation
|
||||
|
||||
|
||||
def iconSvg(name):
|
||||
@ -43,7 +43,7 @@ def obligatedFundingGraphWidth(values):
|
||||
numerator, denominator = values
|
||||
try:
|
||||
return (numerator / denominator) * 100
|
||||
except DivisionByZeroException:
|
||||
except (DivisionByZeroException, InvalidOperation):
|
||||
return 0
|
||||
|
||||
|
||||
|
@ -24,6 +24,8 @@ class AzureStages(Enum):
|
||||
TENANT_PRINCIPAL_CREDENTIAL = "tenant principal credential"
|
||||
ADMIN_ROLE_DEFINITION = "admin role definition"
|
||||
PRINCIPAL_ADMIN_ROLE = "tenant principal admin"
|
||||
INITIAL_MGMT_GROUP = "initial management group"
|
||||
INITIAL_MGMT_GROUP_VERIFICATION = "initial management group verification"
|
||||
TENANT_ADMIN_OWNERSHIP = "tenant admin ownership"
|
||||
TENANT_PRINCIPAL_OWNERSHIP = "tenant principial ownership"
|
||||
|
||||
|
@ -89,6 +89,12 @@ class Portfolio(
|
||||
def active_task_orders(self):
|
||||
return [task_order for task_order in self.task_orders if task_order.is_active]
|
||||
|
||||
@property
|
||||
def total_obligated_funds(self):
|
||||
return sum(
|
||||
(task_order.total_obligated_funds for task_order in self.active_task_orders)
|
||||
)
|
||||
|
||||
@property
|
||||
def funding_duration(self):
|
||||
"""
|
||||
|
@ -155,37 +155,38 @@ class PortfolioStateMachine(
|
||||
print(exc.json())
|
||||
app.logger.info(payload)
|
||||
self.fail_stage(stage)
|
||||
else:
|
||||
# TODO: Determine best place to do this, maybe @reconstructor
|
||||
self.csp = app.csp.cloud
|
||||
|
||||
# TODO: Determine best place to do this, maybe @reconstructor
|
||||
self.csp = app.csp.cloud
|
||||
try:
|
||||
func_name = f"create_{stage}"
|
||||
response = getattr(self.csp, func_name)(payload_data)
|
||||
if self.portfolio.csp_data is None:
|
||||
self.portfolio.csp_data = {}
|
||||
self.portfolio.csp_data.update(response.dict())
|
||||
db.session.add(self.portfolio)
|
||||
db.session.commit()
|
||||
except PydanticValidationError as exc:
|
||||
app.logger.error(
|
||||
f"Failed to cast response to valid result class {self.__repr__()}:",
|
||||
exc_info=1,
|
||||
)
|
||||
app.logger.info(exc.json())
|
||||
print(exc.json())
|
||||
app.logger.info(payload_data)
|
||||
# TODO: Ensure that failing the stage does not preclude a Celery retry
|
||||
self.fail_stage(stage)
|
||||
# TODO: catch and handle general CSP exception here
|
||||
except (ConnectionException, UnknownServerException) as exc:
|
||||
app.logger.error(
|
||||
f"CSP api call. Caught exception for {self.__repr__()}.",
|
||||
exc_info=1,
|
||||
)
|
||||
# TODO: Ensure that failing the stage does not preclude a Celery retry
|
||||
self.fail_stage(stage)
|
||||
|
||||
try:
|
||||
func_name = f"create_{stage}"
|
||||
response = getattr(self.csp, func_name)(payload_data)
|
||||
if self.portfolio.csp_data is None:
|
||||
self.portfolio.csp_data = {}
|
||||
self.portfolio.csp_data.update(response.dict())
|
||||
db.session.add(self.portfolio)
|
||||
db.session.commit()
|
||||
except PydanticValidationError as exc:
|
||||
app.logger.error(
|
||||
f"Failed to cast response to valid result class {self.__repr__()}:",
|
||||
exc_info=1,
|
||||
)
|
||||
app.logger.info(exc.json())
|
||||
print(exc.json())
|
||||
app.logger.info(payload_data)
|
||||
# TODO: Ensure that failing the stage does not preclude a Celery retry
|
||||
self.fail_stage(stage)
|
||||
# TODO: catch and handle general CSP exception here
|
||||
except (ConnectionException, UnknownServerException) as exc:
|
||||
app.logger.error(
|
||||
f"CSP api call. Caught exception for {self.__repr__()}.", exc_info=1,
|
||||
)
|
||||
# TODO: Ensure that failing the stage does not preclude a Celery retry
|
||||
self.fail_stage(stage)
|
||||
|
||||
self.finish_stage(stage)
|
||||
self.finish_stage(stage)
|
||||
|
||||
def is_csp_data_valid(self, event):
|
||||
"""
|
||||
|
@ -25,7 +25,6 @@ SORT_ORDERING = [
|
||||
Status.DRAFT,
|
||||
Status.UPCOMING,
|
||||
Status.EXPIRED,
|
||||
Status.UNSIGNED,
|
||||
]
|
||||
|
||||
|
||||
@ -148,7 +147,10 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
|
||||
@property
|
||||
def display_status(self):
|
||||
return self.status.value
|
||||
if self.status == Status.UNSIGNED:
|
||||
return Status.DRAFT.value
|
||||
else:
|
||||
return self.status.value
|
||||
|
||||
@property
|
||||
def portfolio_name(self):
|
||||
|
@ -34,25 +34,25 @@ def create_portfolio():
|
||||
@user_can(Permissions.VIEW_PORTFOLIO_REPORTS, message="view portfolio reports")
|
||||
def reports(portfolio_id):
|
||||
portfolio = Portfolios.get(g.current_user, portfolio_id)
|
||||
spending = Reports.get_portfolio_spending(portfolio)
|
||||
obligated = portfolio.total_obligated_funds
|
||||
remaining = obligated - (spending["invoiced"] + spending["estimated"])
|
||||
|
||||
current_obligated_funds = Reports.obligated_funds_by_JEDI_clin(portfolio)
|
||||
current_obligated_funds = {
|
||||
**spending,
|
||||
"obligated": obligated,
|
||||
"remaining": remaining,
|
||||
}
|
||||
|
||||
if any(map(lambda clin: clin["remaining"] < 0, current_obligated_funds)):
|
||||
if current_obligated_funds["remaining"] < 0:
|
||||
flash("insufficient_funds")
|
||||
|
||||
# wrapped in str() because the sum of obligated funds returns a Decimal object
|
||||
total_portfolio_value = str(
|
||||
sum(
|
||||
task_order.total_obligated_funds
|
||||
for task_order in portfolio.active_task_orders
|
||||
)
|
||||
)
|
||||
return render_template(
|
||||
"portfolios/reports/index.html",
|
||||
portfolio=portfolio,
|
||||
total_portfolio_value=total_portfolio_value,
|
||||
# wrapped in str() because the sum of obligated funds returns a Decimal object
|
||||
total_portfolio_value=str(portfolio.total_obligated_funds),
|
||||
current_obligated_funds=current_obligated_funds,
|
||||
expired_task_orders=Reports.expired_task_orders(portfolio),
|
||||
monthly_spending=Reports.monthly_spending(portfolio),
|
||||
retrieved=datetime.now(), # mocked datetime of reporting data retrival
|
||||
)
|
||||
|
@ -48,6 +48,7 @@ PGUSER = postgres
|
||||
PORT=8000
|
||||
REDIS_HOST=localhost:6379
|
||||
REDIS_PASSWORD
|
||||
REDIS_SSLMODE
|
||||
REDIS_TLS=False
|
||||
REDIS_USER
|
||||
SECRET_KEY = change_me_into_something_secret
|
||||
|
@ -10,6 +10,5 @@ resources:
|
||||
- volume-claim.yml
|
||||
- nginx-client-ca-bundle.yml
|
||||
- acme-challenges.yml
|
||||
- aadpodidentity.yml
|
||||
- nginx-snippets.yml
|
||||
- autoscaling.yml
|
||||
|
@ -4,19 +4,30 @@ kind: ConfigMap
|
||||
metadata:
|
||||
name: atst-worker-envvars
|
||||
data:
|
||||
AZURE_ACCOUNT_NAME: jeditasksatat
|
||||
CELERY_DEFAULT_QUEUE: celery-staging
|
||||
SERVER_NAME: staging.atat.code.mil
|
||||
FLASK_ENV: staging
|
||||
PGDATABASE: cloudzero_jedidev_atat
|
||||
PGHOST: 191.238.6.43
|
||||
PGUSER: atat@cloudzero-jedidev-sql
|
||||
PGSSLMODE: require
|
||||
REDIS_HOST: 10.1.3.34:6380
|
||||
SERVER_NAME: dev.atat.cloud.mil
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: atst-envvars
|
||||
data:
|
||||
ASSETS_URL: https://atat-cdn-staging.azureedge.net/
|
||||
CDN_ORIGIN: https://staging.atat.code.mil
|
||||
ASSETS_URL: ""
|
||||
AZURE_ACCOUNT_NAME: jeditasksatat
|
||||
CAC_URL: https://auth-dev.atat.cloud.mil
|
||||
CDN_ORIGIN: https://dev.atat.cloud.mil
|
||||
CELERY_DEFAULT_QUEUE: celery-staging
|
||||
FLASK_ENV: staging
|
||||
STATIC_URL: https://atat-cdn-staging.azureedge.net/static/
|
||||
PGHOST: cloudzero-dev-sql.postgres.database.azure.com
|
||||
REDIS_HOST: cloudzero-dev-redis.redis.cache.windows.net:6380
|
||||
PGDATABASE: cloudzero_jedidev_atat
|
||||
PGHOST: 191.238.6.43
|
||||
PGUSER: atat@cloudzero-jedidev-sql
|
||||
PGSSLMODE: require
|
||||
REDIS_HOST: 10.1.3.34:6380
|
||||
SESSION_COOKIE_DOMAIN: atat.cloud.mil
|
||||
|
@ -9,23 +9,19 @@ spec:
|
||||
- name: nginx-secret
|
||||
flexVolume:
|
||||
options:
|
||||
keyvaultname: "cloudzero-dev-keyvault"
|
||||
# keyvaultobjectnames: "dhparam4096;cert;cert"
|
||||
keyvaultobjectnames: "foo"
|
||||
keyvaultobjectaliases: "FOO"
|
||||
keyvaultobjecttypes: "secret"
|
||||
usevmmanagedidentity: "true"
|
||||
usepodidentity: "false"
|
||||
usevmmanagedidentity: "true"
|
||||
vmmanagedidentityclientid: $VMSS_CLIENT_ID
|
||||
keyvaultname: "cz-jedidev-keyvault"
|
||||
keyvaultobjectnames: "dhparam4096;ATATCERT;ATATCERT"
|
||||
- name: flask-secret
|
||||
flexVolume:
|
||||
options:
|
||||
keyvaultname: "cloudzero-dev-keyvault"
|
||||
# keyvaultobjectnames: "AZURE-STORAGE-KEY;MAIL-PASSWORD;PGPASSWORD;REDIS-PASSWORD;SECRET-KEY"
|
||||
keyvaultobjectnames: "master-PGPASSWORD"
|
||||
keyvaultobjectaliases: "PGPASSWORD"
|
||||
keyvaultobjecttypes: "secret"
|
||||
usevmmanagedidentity: "true"
|
||||
usepodidentity: "false"
|
||||
usevmmanagedidentity: "true"
|
||||
vmmanagedidentityclientid: $VMSS_CLIENT_ID
|
||||
keyvaultname: "cz-jedidev-keyvault"
|
||||
keyvaultobjectnames: "AZURE-STORAGE-KEY;MAIL-PASSWORD;PGPASSWORD;REDIS-PASSWORD;SECRET-KEY"
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
@ -38,10 +34,11 @@ spec:
|
||||
- name: flask-secret
|
||||
flexVolume:
|
||||
options:
|
||||
keyvaultname: "cloudzero-dev-keyvault"
|
||||
keyvaultobjectnames: "AZURE-STORAGE-KEY;MAIL-PASSWORD;PGPASSWORD;REDIS-PASSWORD;SECRET-KEY"
|
||||
usevmmanagedidentity: "true"
|
||||
usepodidentity: "false"
|
||||
usevmmanagedidentity: "true"
|
||||
vmmanagedidentityclientid: $VMSS_CLIENT_ID
|
||||
keyvaultname: "cz-jedidev-keyvault"
|
||||
keyvaultobjectnames: "AZURE-STORAGE-KEY;MAIL-PASSWORD;PGPASSWORD;REDIS-PASSWORD;SECRET-KEY"
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
@ -54,10 +51,11 @@ spec:
|
||||
- name: flask-secret
|
||||
flexVolume:
|
||||
options:
|
||||
keyvaultname: "cloudzero-dev-keyvault"
|
||||
keyvaultobjectnames: "AZURE-STORAGE-KEY;MAIL-PASSWORD;PGPASSWORD;REDIS-PASSWORD;SECRET-KEY"
|
||||
usevmmanagedidentity: "true"
|
||||
usepodidentity: "false"
|
||||
usevmmanagedidentity: "true"
|
||||
vmmanagedidentityclientid: $VMSS_CLIENT_ID
|
||||
keyvaultname: "cz-jedidev-keyvault"
|
||||
keyvaultobjectnames: "AZURE-STORAGE-KEY;MAIL-PASSWORD;PGPASSWORD;REDIS-PASSWORD;SECRET-KEY"
|
||||
---
|
||||
apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
@ -72,7 +70,8 @@ spec:
|
||||
- name: flask-secret
|
||||
flexVolume:
|
||||
options:
|
||||
keyvaultname: "cloudzero-dev-keyvault"
|
||||
keyvaultobjectnames: "AZURE-STORAGE-KEY;MAIL-PASSWORD;PGPASSWORD;REDIS-PASSWORD;SECRET-KEY"
|
||||
usevmmanagedidentity: "true"
|
||||
usepodidentity: "false"
|
||||
usevmmanagedidentity: "true"
|
||||
vmmanagedidentityclientid: $VMSS_CLIENT_ID
|
||||
keyvaultname: "cz-jedidev-keyvault"
|
||||
keyvaultobjectnames: "AZURE-STORAGE-KEY;MAIL-PASSWORD;PGPASSWORD;REDIS-PASSWORD;SECRET-KEY"
|
||||
|
@ -1,9 +1,8 @@
|
||||
namespace: staging
|
||||
namespace: cloudzero-dev
|
||||
bases:
|
||||
- ../../azure/
|
||||
resources:
|
||||
- namespace.yml
|
||||
- reset-cron-job.yml
|
||||
patchesStrategicMerge:
|
||||
- ports.yml
|
||||
- envvars.yml
|
||||
|
@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: staging
|
||||
name: cloudzero-dev
|
||||
|
@ -5,7 +5,7 @@ metadata:
|
||||
name: atst-main
|
||||
annotations:
|
||||
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
|
||||
service.beta.kubernetes.io/azure-load-balancer-internal-subnet: "cloudzero-dev-public"
|
||||
service.beta.kubernetes.io/azure-load-balancer-internal-subnet: "cloudzero-jedidev-public"
|
||||
spec:
|
||||
loadBalancerIP: ""
|
||||
ports:
|
||||
@ -22,7 +22,7 @@ metadata:
|
||||
name: atst-auth
|
||||
annotations:
|
||||
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
|
||||
service.beta.kubernetes.io/azure-load-balancer-internal-subnet: "cloudzero-dev-public"
|
||||
service.beta.kubernetes.io/azure-load-balancer-internal-subnet: "cloudzero-jedidev-public"
|
||||
spec:
|
||||
loadBalancerIP: ""
|
||||
ports:
|
||||
|
@ -1,46 +0,0 @@
|
||||
apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: reset-db
|
||||
namespace: atat
|
||||
spec:
|
||||
schedule: "0 4 * * *"
|
||||
concurrencyPolicy: Replace
|
||||
successfulJobsHistoryLimit: 1
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: atst
|
||||
role: reset-db
|
||||
aadpodidbinding: atat-kv-id-binding
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: reset
|
||||
image: $CONTAINER_IMAGE
|
||||
command: [
|
||||
"/bin/sh", "-c"
|
||||
]
|
||||
args: [
|
||||
"/opt/atat/atst/.venv/bin/python",
|
||||
"/opt/atat/atst/script/reset_database.py"
|
||||
]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: atst-worker-envvars
|
||||
volumeMounts:
|
||||
- name: flask-secret
|
||||
mountPath: "/config"
|
||||
volumes:
|
||||
- name: flask-secret
|
||||
flexVolume:
|
||||
driver: "azure/kv"
|
||||
options:
|
||||
usepodidentity: "true"
|
||||
keyvaultname: "atat-vault-test"
|
||||
keyvaultobjectnames: "staging-AZURE-STORAGE-KEY;staging-MAIL-PASSWORD;staging-PGPASSWORD;staging-REDIS-PASSWORD;staging-SECRET-KEY"
|
||||
keyvaultobjectaliases: "AZURE_STORAGE_KEY;MAIL_PASSWORD;PGPASSWORD;REDIS_PASSWORD;SECRET_KEY"
|
||||
keyvaultobjecttypes: "secret;secret;secret;secret;key"
|
||||
tenantid: $TENANT_ID
|
@ -0,0 +1,5 @@
|
||||
namespace: cloudzero-dev
|
||||
bases:
|
||||
- ../../shared/
|
||||
patchesStrategicMerge:
|
||||
- migration.yaml
|
16
deploy/overlays/migration-cloudzero-dev/migration.yaml
Normal file
16
deploy/overlays/migration-cloudzero-dev/migration.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: migration
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- name: flask-secret
|
||||
flexVolume:
|
||||
options:
|
||||
usepodidentity: "false"
|
||||
usevmmanagedidentity: "true"
|
||||
vmmanagedidentityclientid: $VMSS_CLIENT_ID
|
||||
keyvaultname: "cz-jedidev-keyvault"
|
||||
keyvaultobjectnames: "AZURE-STORAGE-KEY;MAIL-PASSWORD;PGPASSWORD;REDIS-PASSWORD;SECRET-KEY"
|
3
deploy/shared/kustomization.yaml
Normal file
3
deploy/shared/kustomization.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
namespace: atat
|
||||
resources:
|
||||
- migration.yaml
|
@ -34,8 +34,10 @@ export default {
|
||||
|
||||
methods: {
|
||||
next: function() {
|
||||
this.submitted = true
|
||||
if (this.validateFields()) {
|
||||
this.step += 1
|
||||
this.submitted = false
|
||||
}
|
||||
},
|
||||
previous: function() {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import ExpandSidenavMixin from '../mixins/expand_sidenav'
|
||||
import ToggleMixin from '../mixins/toggle'
|
||||
import { sidenavCookieName } from '../lib/constants'
|
||||
|
||||
export default {
|
||||
name: 'sidenav-toggler',
|
||||
@ -14,7 +15,7 @@ export default {
|
||||
toggle: function(e) {
|
||||
e.preventDefault()
|
||||
this.isVisible = !this.isVisible
|
||||
document.cookie = this.cookieName + '=' + this.isVisible + '; path=/'
|
||||
document.cookie = sidenavCookieName + '=' + this.isVisible + '; path=/'
|
||||
this.$parent.$emit('sidenavToggle', this.isVisible)
|
||||
},
|
||||
},
|
||||
|
@ -5,6 +5,13 @@ export default {
|
||||
|
||||
mixins: [ToggleMixin],
|
||||
|
||||
props: {
|
||||
defaultVisible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle: function(e) {
|
||||
if (this.$el.contains(e.target)) {
|
||||
|
1
js/lib/constants.js
Normal file
1
js/lib/constants.js
Normal file
@ -0,0 +1 @@
|
||||
export const sidenavCookieName = 'expandSidenav'
|
@ -1,11 +1,12 @@
|
||||
import { sidenavCookieName } from '../lib/constants'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
cookieName: 'expandSidenav',
|
||||
defaultVisible: {
|
||||
type: Boolean,
|
||||
default: function() {
|
||||
if (document.cookie.match(this.cookieName)) {
|
||||
return !!document.cookie.match(this.cookieName + ' *= *true')
|
||||
if (document.cookie.match(sidenavCookieName)) {
|
||||
return !!document.cookie.match(sidenavCookieName + ' *= *true')
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ export default {
|
||||
return {
|
||||
changed: this.hasChanges,
|
||||
valid: false,
|
||||
submitted: false,
|
||||
}
|
||||
},
|
||||
|
||||
@ -36,15 +37,16 @@ export default {
|
||||
handleSubmit: function(event) {
|
||||
if (!this.valid) {
|
||||
event.preventDefault()
|
||||
this.submitted = true
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
canSave: function() {
|
||||
if (this.changed && this.valid) {
|
||||
if (this.changed && this.valid && !this.submitted) {
|
||||
return true
|
||||
} else if (this.enableSave && this.valid) {
|
||||
} else if (this.enableSave && this.valid && !this.submitted) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
@ -16,16 +16,14 @@ from reset_database import reset_database
|
||||
|
||||
|
||||
def database_setup(username, password, dbname, ccpo_users):
|
||||
print("Applying schema and seeding roles and permissions.")
|
||||
reset_database()
|
||||
|
||||
print(
|
||||
f"Creating Postgres user role for '{username}' and granting all privileges to database '{dbname}'."
|
||||
)
|
||||
try:
|
||||
_create_database_user(username, password, dbname)
|
||||
except sqlalchemy.exc.ProgrammingError as err:
|
||||
print(f"Postgres user role '{username}' already exists.")
|
||||
_create_database_user(username, password, dbname)
|
||||
|
||||
print("Applying schema and seeding roles and permissions.")
|
||||
reset_database()
|
||||
print("Creating initial set of CCPO users.")
|
||||
_add_ccpo_users(ccpo_users)
|
||||
|
||||
@ -47,6 +45,22 @@ def _create_database_user(username, password, dbname):
|
||||
f"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON FUNCTIONS TO {username}; \n"
|
||||
)
|
||||
|
||||
try:
|
||||
# TODO: make this more configurable
|
||||
engine.execute(f"GRANT {username} TO azure_pg_admin;")
|
||||
except sqlalchemy.exc.ProgrammingError as err:
|
||||
print(f"Cannot grant new role {username} to azure_pg_admin")
|
||||
|
||||
for table in meta.tables:
|
||||
engine.execute(f"ALTER TABLE {table} OWNER TO {username};\n")
|
||||
|
||||
sequence_results = engine.execute(
|
||||
"SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';"
|
||||
).fetchall()
|
||||
sequences = [p[0] for p in sequence_results]
|
||||
for sequence in sequences:
|
||||
engine.execute(f"ALTER SEQUENCE {sequence} OWNER TO {username};\n")
|
||||
|
||||
trans.commit()
|
||||
|
||||
|
||||
|
@ -13,6 +13,7 @@ SETTINGS=(
|
||||
AUTH_DOMAIN
|
||||
KV_MI_ID
|
||||
KV_MI_CLIENT_ID
|
||||
VMSS_CLIENT_ID
|
||||
TENANT_ID
|
||||
)
|
||||
|
||||
|
@ -6,8 +6,12 @@
|
||||
heading_tag="h2",
|
||||
heading_classes="",
|
||||
content_tag="div",
|
||||
content_classes="") %}
|
||||
<accordion v-cloak inline-template>
|
||||
content_classes="",
|
||||
default_visible=False) %}
|
||||
<accordion
|
||||
v-cloak
|
||||
inline-template
|
||||
v-bind:default-visible='{{ default_visible | string | lower }}'>
|
||||
<{{wrapper_tag}} class="{{ wrapper_classes }}">
|
||||
<{{heading_tag}} class="accordion__button {{ heading_classes }}">
|
||||
<button
|
||||
|
@ -16,13 +16,12 @@
|
||||
<th>PoP</th>
|
||||
<th>CLIN Value</th>
|
||||
<th>Amount Obligated</th>
|
||||
<th>Amount Unspent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for task_order in expired_task_orders %}
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<td colspan="4">
|
||||
<span class="h4 reporting-expended-funding__header">Task Order</span> <a href="{{ url_for("task_orders.view_task_order", task_order_id=task_order.id) }}">
|
||||
{{ task_order.number }} {{ Icon("caret_right", classes="icon--tiny icon--blue" ) }}
|
||||
</a>
|
||||
@ -39,9 +38,8 @@
|
||||
-
|
||||
{{ clin.end_date | formattedDate(formatter="%b %d, %Y") }}
|
||||
</td>
|
||||
<td>{{ clin.total_amount | dollars }}</td>
|
||||
<td>{{ clin.obligated_amount | dollars }}</td>
|
||||
<td>{{ 0 | dollars }}</td>
|
||||
<td class="table-cell--align-right">{{ clin.total_amount | dollars }}</td>
|
||||
<td class="table-cell--align-right">{{ clin.obligated_amount | dollars }}</td>
|
||||
<tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
@ -13,7 +13,5 @@
|
||||
<hr>
|
||||
{% include "portfolios/reports/obligated_funds.html" %}
|
||||
{% include "portfolios/reports/expired_task_orders.html" %}
|
||||
<hr>
|
||||
{% include "portfolios/reports/application_and_env_spending.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -7,61 +7,56 @@
|
||||
</header>
|
||||
<div class='panel'>
|
||||
<div class='panel__content jedi-clin-funding'>
|
||||
{% for JEDI_clin in current_obligated_funds | sort(attribute='name')%}
|
||||
<div class="jedi-clin-funding__clin-wrapper">
|
||||
<h3 class="h5 jedi-clin-funding__header">
|
||||
{{ "JEDICLINType.{}".format(JEDI_clin.name) | translate }}
|
||||
</h3>
|
||||
<p class="jedi-clin-funding__subheader">Total obligated amount: {{ JEDI_clin.obligated | dollars }}</p>
|
||||
<div class="jedi-clin-funding__graph">
|
||||
{% if JEDI_clin.remaining < 0 %}
|
||||
<span style="width:100%" class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--insufficient"></span>
|
||||
{% else %}
|
||||
{% set invoiced_width = (JEDI_clin.invoiced, JEDI_clin.obligated) | obligatedFundingGraphWidth %}
|
||||
{% if invoiced_width %}
|
||||
<span style="width:{{ invoiced_width }}%"
|
||||
class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--invoiced">
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% set estimated_width = (JEDI_clin.estimated, JEDI_clin.obligated) | obligatedFundingGraphWidth %}
|
||||
{% if estimated_width %}
|
||||
<span style="width:{{ (JEDI_clin.estimated, JEDI_clin.obligated) | obligatedFundingGraphWidth }}%"
|
||||
class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--estimated">
|
||||
</span>
|
||||
{% endif %}
|
||||
<span style="width:{{ (JEDI_clin.remaining, JEDI_clin.obligated) | obligatedFundingGraphWidth }}%"
|
||||
class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--remaining">
|
||||
<div class="jedi-clin-funding__clin-wrapper">
|
||||
<h3 class="h5 jedi-clin-funding__header">
|
||||
Total obligated amount: {{ current_obligated_funds.obligated | dollars }}
|
||||
</h3>
|
||||
<div class="jedi-clin-funding__graph">
|
||||
{% if current_obligated_funds.remaining < 0 %}
|
||||
<span style="width:100%" class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--insufficient"></span>
|
||||
{% else %}
|
||||
{% set invoiced_width = (current_obligated_funds.invoiced, current_obligated_funds.obligated) | obligatedFundingGraphWidth %}
|
||||
{% if invoiced_width %}
|
||||
<span style="width:{{ invoiced_width }}%"
|
||||
class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--invoiced">
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% set estimated_width = (current_obligated_funds.estimated, current_obligated_funds.obligated) | obligatedFundingGraphWidth %}
|
||||
{% if estimated_width %}
|
||||
<span style="width:{{ (current_obligated_funds.estimated, current_obligated_funds.obligated) | obligatedFundingGraphWidth }}%"
|
||||
class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--estimated">
|
||||
</span>
|
||||
{% endif %}
|
||||
<span style="width:{{ (current_obligated_funds.remaining, current_obligated_funds.obligated) | obligatedFundingGraphWidth }}%"
|
||||
class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--remaining">
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="jedi-clin-funding__graph-values">
|
||||
<div class="jedi-clin-funding__meta">
|
||||
<p class="jedi-clin-funding__meta-header">
|
||||
<span class="jedi-clin-funding__meta-key jedi-clin-funding__meta-key--invoiced"></span>
|
||||
Invoiced expended funds:
|
||||
</p>
|
||||
<p class="h3 jedi-clin-funding__meta-value">{{ current_obligated_funds.invoiced | dollars }}</p>
|
||||
</div>
|
||||
<div class="jedi-clin-funding__graph-values">
|
||||
<div class="jedi-clin-funding__meta">
|
||||
<p class="jedi-clin-funding__meta-header">
|
||||
<span class="jedi-clin-funding__meta-key jedi-clin-funding__meta-key--invoiced"></span>
|
||||
Invoiced expended funds:
|
||||
</p>
|
||||
<p class="h3 jedi-clin-funding__meta-value">{{ JEDI_clin.invoiced | dollars }}</p>
|
||||
</div>
|
||||
<div class="jedi-clin-funding__meta">
|
||||
<p class="jedi-clin-funding__meta-header">
|
||||
<span class="jedi-clin-funding__meta-key jedi-clin-funding__meta-key--estimated"></span>
|
||||
Estimated expended funds:
|
||||
</p>
|
||||
<p class="h3 jedi-clin-funding__meta-value">{{ JEDI_clin.estimated | dollars }}</p>
|
||||
</div>
|
||||
<div class="jedi-clin-funding__meta">
|
||||
<p class="jedi-clin-funding__meta-header">
|
||||
<span class="jedi-clin-funding__meta-key jedi-clin-funding__meta-key--{{"remaining" if JEDI_clin.remaining > 0 else "insufficient"}}"></span>
|
||||
Remaining funds:
|
||||
</p>
|
||||
<p class="h3 jedi-clin-funding__meta-value {% if JEDI_clin.remaining < 0 %}text-danger{% endif %}">{{ JEDI_clin.remaining | dollars }}</p>
|
||||
</div>
|
||||
<div class="jedi-clin-funding__meta">
|
||||
<p class="jedi-clin-funding__meta-header">
|
||||
<span class="jedi-clin-funding__meta-key jedi-clin-funding__meta-key--estimated"></span>
|
||||
Estimated expended funds:
|
||||
</p>
|
||||
<p class="h3 jedi-clin-funding__meta-value">{{ current_obligated_funds.estimated | dollars }}</p>
|
||||
</div>
|
||||
<div class="jedi-clin-funding__meta">
|
||||
<p class="jedi-clin-funding__meta-header">
|
||||
<span class="jedi-clin-funding__meta-key jedi-clin-funding__meta-key--{{"remaining" if current_obligated_funds.remaining > 0 else "insufficient"}}"></span>
|
||||
Remaining funds:
|
||||
</p>
|
||||
<p class="h3 jedi-clin-funding__meta-value {% if current_obligated_funds.remaining < 0 %}text-danger{% endif %}">{{ current_obligated_funds.remaining | dollars }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="jedi-clin-funding__active-task-orders">
|
||||
<h3 class="h4">
|
||||
Active Task Orders
|
||||
|
@ -14,9 +14,15 @@
|
||||
|
||||
|
||||
{% macro TaskOrderList(task_orders, status) %}
|
||||
{% set show_task_orders = task_orders|length > 0 %}
|
||||
<div class="accordion">
|
||||
{% call Accordion(title=("task_orders.status_list_title"|translate({'status': status})), id=status, heading_tag="h4") %}
|
||||
{% if task_orders|length > 0 %}
|
||||
{% call Accordion(
|
||||
title=("task_orders.status_list_title"|translate({'status': status})),
|
||||
id=status,
|
||||
heading_tag="h4",
|
||||
default_visible=show_task_orders
|
||||
) %}
|
||||
{% if show_task_orders %}
|
||||
{% for task_order in task_orders %}
|
||||
{% set to_number %}
|
||||
{% if task_order.number != None %}
|
||||
|
2
terraform/.gitignore
vendored
2
terraform/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
.terraform
|
||||
.vscode/
|
@ -1,305 +0,0 @@
|
||||
# ATAT Terraform
|
||||
Welcome! You've found the ATAT IaC configurations.
|
||||
|
||||
ATAT is configured using terraform and a wrapper script called `secrets-tool`. With `terraform` we can configure infrastructure in a programatic way and ensure consistency across environments.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
**modules/** - Terraform modules. These are modules that can be re-used for multiple environments.
|
||||
|
||||
**providers/** - Specific environment configurations. (dev,production, etc)
|
||||
|
||||
# Setup
|
||||
Install the following requirements.
|
||||
|
||||
I highly recommend [tfenv](https://github.com/tfutils/tfenv) which will help you manage versions of TF and install new ones as needed. It gives you the ability to switch back and forth between versions as necessary, especially when doing upgrades and managing multiple environments. Think of it like `pyenv`.
|
||||
|
||||
Python is required for the `secrets-tool`. It is used to wrap terraform and pass secrets in to terraform from Azure KeyVault. This approach avoids leaving secrets on the filesystem in any way and allow for restricting access to secrets to specific operators.
|
||||
|
||||
Azure CLI is necessary for creating some intial resources, but is also used by the Python Azure SDK to make calls in some cases.
|
||||
|
||||
Requirements:
|
||||
- [tfenv](https://github.com/tfutils/tfenv)
|
||||
- Python 3.7
|
||||
- Python pip
|
||||
- Python virtualenv # FIXME: Switch to `pipenv`
|
||||
- [azure cli](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest)
|
||||
- [powershell](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-6) See below
|
||||
|
||||
# tfenv
|
||||
`tfenv` will allow you to install TF versions. For example.
|
||||
|
||||
```
|
||||
tfenv install 0.12.18
|
||||
```
|
||||
_0.12.18 at time of writing_
|
||||
|
||||
|
||||
To select a version to use
|
||||
```
|
||||
tfenv use 0.12.18
|
||||
```
|
||||
|
||||
# Powershell
|
||||
Some things you need to use powershell. Specifically getting client profiles for the VPN.
|
||||
|
||||
## Install powershell on Linux
|
||||
Powershell on recent versions of Ubuntu is available through snap.
|
||||
|
||||
For Ubuntu 19.10
|
||||
```
|
||||
snap install powershell --classic
|
||||
```
|
||||
|
||||
# Preview Features
|
||||
To create all the resources we need for this environment we'll need to enable some _Preview_ features.
|
||||
|
||||
This registers the specific feature for _SystemAssigned_ principals
|
||||
```
|
||||
az feature register --namespace Microsoft.ContainerService --name MSIPreview
|
||||
az feature register --namespace Microsoft.ContainerService --name NodePublicIPPreview
|
||||
```
|
||||
|
||||
To apply the registration, run the following
|
||||
```
|
||||
az provider register -n Microsoft.ContainerService
|
||||
```
|
||||
|
||||
# Running Terraform
|
||||
First, you'll need to log in to Azure. With the Azure CLI installed, you can run the following.
|
||||
|
||||
```
|
||||
az login
|
||||
```
|
||||
|
||||
Next, you'll need to initialize the environment. This process pulls down the terraform provider module from github as well as pulls in the modules that will be used by this provider/environment setup.
|
||||
|
||||
```
|
||||
cd providers/dev/
|
||||
terraform init
|
||||
```
|
||||
|
||||
Once initialized, you can run a plan. A `plan` compares the terraform definitions you have configured in the provider directory (Ex. `providers/dev`) with what is in the shared state file in the Azure Object Storage (which all providers are currently configured for). This then also compares it to the state of the services which are running in Azure.
|
||||
|
||||
If nothing has been applied, you'll see all the resources defined in terraform as all new with a `+` next to the resource name. If the resource exists, but has changed, you'll see a `~` next to the resource and the delta of the change to be applied.
|
||||
|
||||
If you're plan looks good, you can run the apply.
|
||||
```
|
||||
terraform apply
|
||||
```
|
||||
|
||||
Check the output for errors. Sometimes the syntax is valid, but some of the configuration may be wrong and only rejected by the Azure API at run time. If this is the case, fix your mistake, and re-run.
|
||||
|
||||
# After running TF (Manual Steps)
|
||||
|
||||
## VM Scale Set
|
||||
After running terraform, we need to make a manual change to the VM Scale Set that is used in the kubernetes. Terraform has a bug that is not applying this as of `v1.40` of the `azurerm` provider.
|
||||
|
||||
In order to get the `SystemAssigned` identity to be set, it needs to be set manually in the console.
|
||||
|
||||
Navigate to the VM Scale Set for the k8s cluster you're managing (in the console).
|
||||
|
||||

|
||||
_Just click the `Status` to `On`_
|
||||
|
||||
## KeyVault Policy
|
||||
There is a bug (missing feature really) in the `azurerm` terraform provider which exposes the wrong `object_id/principal_id` in the `azurerm_kubernetes_cluster` output. The `id` that it exposes is the `object_id` of the cluster itself, and _not_ the Virtual Machine Scale Set SystemAssigned identity. This needs to be updated manually after running terraform for the first time.
|
||||
|
||||
To update, just edit the `keyvault.tf`. Set the `principal_id` to the `object_id` of the Virtual Machine Scale set. This can be found in the Azure portal, or via cli.
|
||||
|
||||
```
|
||||
az vmss list
|
||||
```
|
||||
In that list, find the scale set for the k8s cluster you're working on. You'll want the value of `principal_id`.
|
||||
|
||||
|
||||
The error looks like the following
|
||||
```
|
||||
Warning FailedMount 8s (x6 over 25s) kubelet, aks-default-54410534-vmss000001 MountVolume.SetUp failed for volume "flask-secret" : mount command failed, status: Failure, reason: /etc/kubernetes/volumeplugins/azure~kv/azurekeyvault-flex
|
||||
volume failed, Access denied. Caller was not found on any access policy. r nCaller: appid=e6651156-7127-432d-9617-4425177c48f1;oid=f9bcbe58-8b73-4957-aee2-133dc3e58063;numgroups=0;iss=https://sts.windows.net/b5ab0e1e-09f8-4258-afb7-fb17654bc5
|
||||
b3/ r nVault: cloudzero-dev-keyvault;location=eastus2 InnerError={code:AccessDenied}
|
||||
```
|
||||
|
||||
Final configuration will look like this.
|
||||
**keyvault.tf**
|
||||
```
|
||||
module "keyvault" {
|
||||
source = "../../modules/keyvault"
|
||||
name = var.name
|
||||
region = var.region
|
||||
owner = var.owner
|
||||
environment = var.environment
|
||||
tenant_id = var.tenant_id
|
||||
principal_id = "f9bcbe58-8b73-4957-aee2-133dc3e58063"
|
||||
}
|
||||
```
|
||||
|
||||
## Setting the Redis key in KeyVault
|
||||
Redis auth is provided by a simple key that is randomly generated by Azure. This is a simple task for `secrets-tool`.
|
||||
|
||||
First, get the key from the portal. You can navigate to the redis cluster, and click on either "Show Keys", or "Access Keys"
|
||||
|
||||

|
||||
|
||||
In order to set the secret, make sure you specify the keyvault that is used by the application. In dev, its simply called "keyvault", where the operator keyvault has a different name.
|
||||
|
||||
```
|
||||
secrets-tool secrets --keyvault https://cloudzero-dev-keyvault.vault.azure.net/ create --key REDIS-PASSWORD --value "<redis key>"
|
||||
```
|
||||
You'll see output similar to the following if it was successful
|
||||
|
||||
```
|
||||
2020-01-17 14:04:42,996 - utils.keyvault.secrets - DEBUG - Set value for key: REDIS-PASSWORD
|
||||
```
|
||||
|
||||
## Setting the Azure Storage Key
|
||||
Azure storage is very similar to how Redis has a generated key. This generated key is what is used at the time of writing this doc.
|
||||
|
||||
Grab the key from the "Access Keys" tab on the cloud storage bucket
|
||||
|
||||

|
||||
|
||||
Now create the secret in KeyVault. This secret should also be in the application specific KeyVault.
|
||||
|
||||
```
|
||||
secrets-tool secrets --keyvault https://cloudzero-dev-keyvault.vault.azure.net/ create --key AZURE-STORAGE-KEY --value "<storage key>"
|
||||
```
|
||||
You'll see output similar to the following if it was successful
|
||||
|
||||
```
|
||||
2020-01-17 14:14:59,426 - utils.keyvault.secrets - DEBUG - Set value for key: AZURE-STORAGE-KEY
|
||||
```
|
||||
|
||||
# Shutting down and environment
|
||||
To shutdown and remove an environment completely as to not incur any costs you would need to run a `terraform destroy`.
|
||||
|
||||
```
|
||||
terraform destroy
|
||||
```
|
||||
|
||||
**This will destroy all resources defined in the provider so use with caution!! This will include things like KeyVault, Postgres, and so on. You may lose data!!**
|
||||
|
||||
# Advanced Terraform
|
||||
## Targeted Apply
|
||||
Sometimes you're writing a new module and don't want to make changes to anything else. In this case you can limit what TF changes.
|
||||
|
||||
```
|
||||
terraform plan -target=module.vpc
|
||||
```
|
||||
|
||||
In the above example, this will only run a plan (plan/apply/destroy) on the specific module. This can be a module, or resource. You can get a list of module and resources by running `terraform show`.
|
||||
|
||||
# VPN Setup
|
||||
[Configure OpenVPN clients for Azure VPN Gateway](https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-howto-openvpn-clients#before-you-begin)
|
||||
[About P2S VPN client profiles](https://docs.microsoft.com/en-us/azure/vpn-gateway/about-vpn-profile-download)
|
||||
[Configure a VPN client for P2S OpenVPN protocol connections: Azure AD authentication (Preview)](https://docs.microsoft.com/en-us/azure/vpn-gateway/openvpn-azure-ad-client)
|
||||
[Create an Azure Active Directory tenant for P2S OpenVPN protocol connections](https://docs.microsoft.com/en-us/azure/vpn-gateway/openvpn-azure-ad-tenant)
|
||||
|
||||
The docs above should help with client configuration. The last doc (Create an Azure Active Directory..) is necessary to run the command to add the VPN app for AD.
|
||||
|
||||
Copied here for convenience. Just enter this in your browser.
|
||||
```
|
||||
# For Public Azure - Government has a different URL, see doc above
|
||||
https://login.microsoftonline.com/common/oauth2/authorize?client_id=41b23e61-6c1e-4545-b367-cd054e0ed4b4&response_type=code&redirect_uri=https://portal.azure.com&nonce=1234&prompt=admin_consent
|
||||
```
|
||||
|
||||
## Adding a client
|
||||
TODO
|
||||
|
||||
## Downloading a client profile
|
||||
TODO
|
||||
|
||||
# Quick Steps
|
||||
Copy paste (mostly)
|
||||
|
||||
*Register Preview features*
|
||||
See [Registering Features](#Preview_Features)
|
||||
|
||||
*Edit provider.tf and turn off remote bucket temporarily (comment out backend {} section)*
|
||||
```
|
||||
provider "azurerm" {
|
||||
version = "=1.40.0"
|
||||
}
|
||||
|
||||
provider "azuread" {
|
||||
# Whilst version is optional, we /strongly recommend/ using it to pin the version of the Provider being used
|
||||
version = "=0.7.0"
|
||||
}
|
||||
|
||||
terraform {
|
||||
#backend "azurerm" {
|
||||
#resource_group_name = "cloudzero-dev-tfstate"
|
||||
#storage_account_name = "cloudzerodevtfstate"
|
||||
#container_name = "tfstate"
|
||||
#key = "dev.terraform.tfstate"
|
||||
#}
|
||||
}
|
||||
```
|
||||
|
||||
`terraform init`
|
||||
|
||||
`terraform plan -target=module.tf_state`
|
||||
|
||||
Ensure the state bucket is created.
|
||||
|
||||
*create the container in the portal (or cli).*
|
||||
This simply involves going to the bucket in the azure portal and creating the container.
|
||||
|
||||
Now is the tricky part. For this, we will be switching from local state (files) to remote state (stored in the azure bucket)
|
||||
|
||||
Uncomment the `backend {}` section in the `provider.tf` file. Once uncommented, we will re-run the init. This will attempt to copy the local state to the remote bucket.
|
||||
|
||||
`terraform init`
|
||||
|
||||
*Say `yes` to the question*
|
||||
|
||||
Now we need to update the Update `variables.tf` with the principals for the users in `admin_users` variable map. If these are not defined yet, just leave it as an empty set.
|
||||
|
||||
Next, we'll create the operator keyvault.
|
||||
|
||||
`terraform plan -target=module.operator_keyvault`
|
||||
|
||||
Next, we'll pre-populate some secrets using the secrets-tool. Follow the install/setup section in the README.md first. Then populate the secrets with a definition file as described in the following link.
|
||||
|
||||
https://github.com/dod-ccpo/atst/tree/staging/terraform/secrets-tool#populating-secrets-from-secrets-definition-file
|
||||
|
||||
*Create service principal for AKS*
|
||||
```
|
||||
az ad sp create-for-rbac
|
||||
```
|
||||
Take note of the output, you'll need it in the next step to store the secret and `client_id` in keyvault.
|
||||
|
||||
This also involves using secrets-tool. Substitute your keyvault url.
|
||||
```
|
||||
secrets-tool secrets --keyvault https://ops-jedidev-keyvault.vault.azure.net/ create --key k8s-client-id --value [value]
|
||||
secrets-tool secrets --keyvault https://ops-jedidev-keyvault.vault.azure.net/ create --key k8s-client-secret --value [value]
|
||||
```
|
||||
|
||||
*Next we'll apply the rest of the TF configuration*
|
||||
|
||||
`terraform plan` # Make sure this looks correct
|
||||
|
||||
`terraform apply`
|
||||
|
||||
*[Configure AD for MFA](https://docs.microsoft.com/en-us/azure/vpn-gateway/openvpn-azure-ad-mfa)*
|
||||
|
||||
*Then we need an instance of the container*
|
||||
|
||||
Change directories to the repo root. Ensure that you've checked out the staging or master branch:
|
||||
|
||||
`docker build . --build-arg CSP=azure -f ./Dockerfile -t atat:latest`
|
||||
|
||||
*Create secrets for ATAT database user*
|
||||
|
||||
Change directories back to terraform/secrets-tool. There is a sample file there. Make sure you know the URL for the aplication Key Vault (distinct from the operator Key Vault). Run:
|
||||
|
||||
`secrets-tool secrets --keyvault [application key vault URL] load -f ./postgres-user.yaml
|
||||
|
||||
*Create the database, database user, schema, and initial data set*
|
||||
|
||||
|
||||
This is discussed in more detail [here](https://github.com/dod-ccpo/atst/tree/staging/terraform/secrets-tool#setting-up-the-initial-atat-database). Be sure to read the requirements section.
|
||||
|
||||
```
|
||||
secrets-tool database --keyvault [operator key vault URL] provision --app-keyvault [application key vault URL] --dbname jedidev-atat --dbhost [database host name] --ccpo-users /full/path/to/users.yml
|
||||
```
|
Binary file not shown.
Before Width: | Height: | Size: 325 KiB |
Binary file not shown.
Before Width: | Height: | Size: 249 KiB |
Binary file not shown.
Before Width: | Height: | Size: 229 KiB |
@ -1,40 +0,0 @@
|
||||
resource "azurerm_resource_group" "bucket" {
|
||||
name = "${var.name}-${var.environment}-${var.service_name}"
|
||||
location = var.region
|
||||
}
|
||||
|
||||
resource "azurerm_storage_account" "bucket" {
|
||||
name = var.service_name
|
||||
resource_group_name = azurerm_resource_group.bucket.name
|
||||
location = azurerm_resource_group.bucket.location
|
||||
account_tier = "Standard"
|
||||
account_replication_type = "LRS"
|
||||
}
|
||||
|
||||
resource "azurerm_storage_account_network_rules" "acls" {
|
||||
resource_group_name = azurerm_resource_group.bucket.name
|
||||
storage_account_name = azurerm_storage_account.bucket.name
|
||||
|
||||
default_action = var.policy
|
||||
|
||||
# Azure Storage CIDR ACLs do not accept /32 CIDR ranges.
|
||||
ip_rules = [
|
||||
for cidr in values(var.whitelist) : cidr
|
||||
]
|
||||
virtual_network_subnet_ids = var.subnet_ids
|
||||
bypass = ["AzureServices"]
|
||||
}
|
||||
|
||||
resource "azurerm_storage_container" "bucket" {
|
||||
name = "content"
|
||||
storage_account_name = azurerm_storage_account.bucket.name
|
||||
container_access_type = var.container_access_type
|
||||
}
|
||||
|
||||
# Added until requisite TF bugs are fixed. Typically this would be configured in the
|
||||
# storage_account resource
|
||||
resource "null_resource" "retention" {
|
||||
provisioner "local-exec" {
|
||||
command = "az storage logging update --account-name ${azurerm_storage_account.bucket.name} --log rwd --services bqt --retention 90"
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "Region this module and resources will be created in"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Unique name for the services in this module"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
type = string
|
||||
description = "Environment these resources reside (prod, dev, staging, etc)"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
type = string
|
||||
description = "Owner of the environment and resources created in this module"
|
||||
}
|
||||
|
||||
variable "container_access_type" {
|
||||
default = "private"
|
||||
description = "Access type for the container (Default: private)"
|
||||
type = string
|
||||
|
||||
}
|
||||
|
||||
variable "service_name" {
|
||||
description = "Name of the service using this bucket"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "subnet_ids" {
|
||||
description = "List of subnet_ids that will have access to this service"
|
||||
type = list
|
||||
}
|
||||
|
||||
variable "policy" {
|
||||
description = "The default policy for the network access rules (Allow/Deny)"
|
||||
default = "Deny"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "whitelist" {
|
||||
type = map
|
||||
description = "A map of whitelisted IPs and CIDR ranges. For single IPs, Azure expects just the IP, NOT a /32."
|
||||
default = {}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
resource "random_id" "server" {
|
||||
keepers = {
|
||||
azi_id = 1
|
||||
}
|
||||
|
||||
byte_length = 8
|
||||
}
|
||||
|
||||
resource "azurerm_resource_group" "cdn" {
|
||||
name = "${var.name}-${var.environment}-cdn"
|
||||
location = var.region
|
||||
}
|
||||
|
||||
resource "azurerm_cdn_profile" "cdn" {
|
||||
name = "${var.name}-${var.environment}-profile"
|
||||
location = azurerm_resource_group.cdn.location
|
||||
resource_group_name = azurerm_resource_group.cdn.name
|
||||
sku = var.sku
|
||||
}
|
||||
|
||||
resource "azurerm_cdn_endpoint" "cdn" {
|
||||
name = "${var.name}-${var.environment}-${random_id.server.hex}"
|
||||
profile_name = azurerm_cdn_profile.cdn.name
|
||||
location = azurerm_resource_group.cdn.location
|
||||
resource_group_name = azurerm_resource_group.cdn.name
|
||||
|
||||
origin {
|
||||
name = "${var.name}-${var.environment}-origin"
|
||||
host_name = var.origin_host_name
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_monitor_diagnostic_setting" "acr_diagnostic" {
|
||||
name = "${var.name}-${var.environment}-acr-diag"
|
||||
target_resource_id = azurerm_cdn_endpoint.cdn.id
|
||||
log_analytics_workspace_id = var.workspace_id
|
||||
log {
|
||||
category = "CoreAnalytics"
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "Region this module and resources will be created in"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Unique name for the services in this module"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
type = string
|
||||
description = "Environment these resources reside (prod, dev, staging, etc)"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
type = string
|
||||
description = "Owner of the environment and resources created in this module"
|
||||
}
|
||||
|
||||
variable "sku" {
|
||||
type = string
|
||||
description = "SKU of which CDN to use"
|
||||
default = "Standard_Verizon"
|
||||
}
|
||||
|
||||
variable "origin_host_name" {
|
||||
type = string
|
||||
description = "Subdomain to use for the origin in requests to the CDN"
|
||||
}
|
||||
|
||||
variable "workspace_id" {
|
||||
description = "Log Analytics Workspace ID for sending logs generated by this resource"
|
||||
type = string
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
locals {
|
||||
whitelist = values(var.whitelist)
|
||||
}
|
||||
|
||||
resource "azurerm_resource_group" "acr" {
|
||||
name = "${var.name}-${var.environment}-acr"
|
||||
location = var.region
|
||||
}
|
||||
|
||||
resource "azurerm_container_registry" "acr" {
|
||||
name = "${var.name}${var.environment}registry" # Alpha Numeric Only
|
||||
resource_group_name = azurerm_resource_group.acr.name
|
||||
location = azurerm_resource_group.acr.location
|
||||
sku = var.sku
|
||||
admin_enabled = var.admin_enabled
|
||||
#georeplication_locations = [azurerm_resource_group.acr.location, var.backup_region]
|
||||
|
||||
network_rule_set {
|
||||
default_action = var.policy
|
||||
|
||||
ip_rule = [
|
||||
for cidr in values(var.whitelist) : {
|
||||
action = "Allow"
|
||||
ip_range = cidr
|
||||
}
|
||||
]
|
||||
# Dynamic rule should work, but doesn't - See https://github.com/hashicorp/terraform/issues/22340#issuecomment-518779733
|
||||
#dynamic "ip_rule" {
|
||||
# for_each = values(var.whitelist)
|
||||
# content {
|
||||
# action = "Allow"
|
||||
# ip_range = ip_rule.value
|
||||
# }
|
||||
#}
|
||||
|
||||
virtual_network = [
|
||||
for subnet in var.subnet_ids : {
|
||||
action = "Allow"
|
||||
subnet_id = subnet
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_monitor_diagnostic_setting" "acr_diagnostic" {
|
||||
name = "${var.name}-${var.environment}-acr-diag"
|
||||
target_resource_id = azurerm_container_registry.acr.id
|
||||
log_analytics_workspace_id = var.workspace_id
|
||||
log {
|
||||
category = "ContainerRegistryRepositoryEvents"
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
log {
|
||||
category = "ContainerRegistryLoginEvents"
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
metric {
|
||||
category = "AllMetrics"
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "Region this module and resources will be created in"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Unique name for the services in this module"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
type = string
|
||||
description = "Environment these resources reside (prod, dev, staging, etc)"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
type = string
|
||||
description = "Owner of the environment and resources created in this module"
|
||||
}
|
||||
|
||||
variable "backup_region" {
|
||||
type = string
|
||||
description = "Backup region for georeplicating the container registry"
|
||||
}
|
||||
|
||||
variable "sku" {
|
||||
type = string
|
||||
description = "SKU to use for the container registry service"
|
||||
default = "Premium"
|
||||
}
|
||||
|
||||
variable "admin_enabled" {
|
||||
type = string
|
||||
description = "Admin enabled? (true/false default: false)"
|
||||
default = false
|
||||
|
||||
}
|
||||
|
||||
variable "subnet_ids" {
|
||||
description = "List of subnet_ids that will have access to this service"
|
||||
type = list
|
||||
}
|
||||
|
||||
variable "policy" {
|
||||
description = "The default policy for the network access rules (Allow/Deny)"
|
||||
default = "Deny"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "whitelist" {
|
||||
type = map
|
||||
description = "A map of whitelisted IPs and CIDR ranges. For single IPs, Azure expects just the IP, NOT a /32."
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "workspace_id" {
|
||||
description = "The Log Analytics Workspace ID"
|
||||
type = string
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
resource "azurerm_resource_group" "k8s" {
|
||||
name = "${var.name}-${var.environment}-vpc"
|
||||
location = var.region
|
||||
}
|
||||
|
||||
resource "azurerm_kubernetes_cluster" "k8s" {
|
||||
name = "${var.name}-${var.environment}-k8s"
|
||||
location = azurerm_resource_group.k8s.location
|
||||
resource_group_name = azurerm_resource_group.k8s.name
|
||||
dns_prefix = var.k8s_dns_prefix
|
||||
|
||||
service_principal {
|
||||
client_id = var.client_id
|
||||
client_secret = var.client_secret
|
||||
}
|
||||
|
||||
default_node_pool {
|
||||
name = "default"
|
||||
vm_size = "Standard_D1_v2"
|
||||
os_disk_size_gb = 30
|
||||
vnet_subnet_id = var.vnet_subnet_id
|
||||
enable_node_public_ip = true # Nodes need a public IP for external resources. FIXME: Switch to NAT Gateway if its available in our subscription
|
||||
enable_auto_scaling = var.enable_auto_scaling
|
||||
max_count = var.max_count # FIXME: if auto_scaling disabled, set to 0
|
||||
min_count = var.min_count # FIXME: if auto_scaling disabled, set to 0
|
||||
}
|
||||
|
||||
identity {
|
||||
type = "SystemAssigned"
|
||||
}
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
default_node_pool.0.node_count
|
||||
]
|
||||
}
|
||||
|
||||
tags = {
|
||||
environment = var.environment
|
||||
owner = var.owner
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_monitor_diagnostic_setting" "k8s_diagnostic-1" {
|
||||
name = "${var.name}-${var.environment}-k8s-diag"
|
||||
target_resource_id = azurerm_kubernetes_cluster.k8s.id
|
||||
log_analytics_workspace_id = var.workspace_id
|
||||
log {
|
||||
category = "kube-apiserver"
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
log {
|
||||
category = "kube-controller-manager"
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
log {
|
||||
category = "kube-scheduler"
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
log {
|
||||
category = "kube-audit"
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
log {
|
||||
category = "cluster-autoscaler"
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
metric {
|
||||
category = "AllMetrics"
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_role_assignment" "k8s_network_contrib" {
|
||||
scope = var.vnet_id
|
||||
role_definition_name = "Network Contributor"
|
||||
principal_id = azurerm_kubernetes_cluster.k8s.identity[0].principal_id
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
output "principal_id" {
|
||||
value = azurerm_kubernetes_cluster.k8s.identity[0].principal_id
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "Region this module and resources will be created in"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Unique name for the services in this module"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
type = string
|
||||
description = "Environment these resources reside (prod, dev, staging, etc)"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
type = string
|
||||
description = "Owner of the environment and resources created in this module"
|
||||
}
|
||||
|
||||
variable "k8s_dns_prefix" {
|
||||
type = string
|
||||
description = "A DNS prefix"
|
||||
}
|
||||
|
||||
variable "k8s_node_size" {
|
||||
type = string
|
||||
description = "The size of the instance to use in the node pools for k8s"
|
||||
default = "Standard_A1_v2"
|
||||
}
|
||||
|
||||
variable "vnet_subnet_id" {
|
||||
description = "Subnet to use for the default k8s pool"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "enable_auto_scaling" {
|
||||
default = false
|
||||
type = string
|
||||
description = "Enable or disable autoscaling (Default: false)"
|
||||
}
|
||||
|
||||
variable "max_count" {
|
||||
default = 1
|
||||
type = string
|
||||
description = "Maximum number of nodes to use in autoscaling. This requires `enable_auto_scaling` to be set to true"
|
||||
|
||||
}
|
||||
|
||||
variable "min_count" {
|
||||
default = 1
|
||||
type = string
|
||||
description = "Minimum number of nodes to use in autoscaling. This requires `enable_auto_scaling` to be set to true"
|
||||
}
|
||||
|
||||
variable "client_id" {
|
||||
type = string
|
||||
description = "The client ID for the Service Principal associated with the AKS cluster."
|
||||
}
|
||||
|
||||
variable "client_secret" {
|
||||
type = string
|
||||
description = "The client secret for the Service Principal associated with the AKS cluster."
|
||||
}
|
||||
|
||||
variable "workspace_id" {
|
||||
description = "Log Analytics workspace for this resource to log to"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "vnet_id" {
|
||||
description = "The ID of the VNET that the AKS cluster app registration needs to provision load balancers in"
|
||||
type = string
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
data "azurerm_client_config" "current" {}
|
||||
|
||||
resource "azurerm_resource_group" "keyvault" {
|
||||
name = "${var.name}-${var.environment}-keyvault"
|
||||
location = var.region
|
||||
}
|
||||
|
||||
resource "azurerm_key_vault" "keyvault" {
|
||||
name = "${var.name}-${var.environment}-keyvault"
|
||||
location = azurerm_resource_group.keyvault.location
|
||||
resource_group_name = azurerm_resource_group.keyvault.name
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
|
||||
sku_name = "premium"
|
||||
|
||||
network_acls {
|
||||
default_action = var.policy
|
||||
bypass = "AzureServices"
|
||||
virtual_network_subnet_ids = var.subnet_ids
|
||||
ip_rules = values(var.whitelist)
|
||||
}
|
||||
|
||||
tags = {
|
||||
environment = var.environment
|
||||
owner = var.owner
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_key_vault_access_policy" "keyvault_k8s_policy" {
|
||||
count = length(var.principal_id) > 0 ? 1 : 0
|
||||
key_vault_id = azurerm_key_vault.keyvault.id
|
||||
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
object_id = var.principal_id
|
||||
|
||||
key_permissions = [
|
||||
"get",
|
||||
]
|
||||
|
||||
secret_permissions = [
|
||||
"get",
|
||||
]
|
||||
}
|
||||
|
||||
# Admin Access
|
||||
resource "azurerm_key_vault_access_policy" "keyvault_admin_policy" {
|
||||
for_each = var.admin_principals
|
||||
key_vault_id = azurerm_key_vault.keyvault.id
|
||||
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
object_id = each.value
|
||||
|
||||
key_permissions = [
|
||||
"get",
|
||||
"list",
|
||||
"create",
|
||||
"update",
|
||||
"delete",
|
||||
]
|
||||
|
||||
secret_permissions = [
|
||||
"get",
|
||||
"list",
|
||||
"set",
|
||||
]
|
||||
|
||||
# backup create delete deleteissuers get getissuers import list listissuers managecontacts manageissuers purge recover restore setissuers update
|
||||
certificate_permissions = [
|
||||
"get",
|
||||
"list",
|
||||
"create",
|
||||
"import",
|
||||
"listissuers",
|
||||
"manageissuers",
|
||||
"deleteissuers",
|
||||
"backup",
|
||||
"update",
|
||||
]
|
||||
}
|
||||
|
||||
resource "azurerm_monitor_diagnostic_setting" "keyvault_diagnostic" {
|
||||
name = "${var.name}-${var.environment}-keyvault-diag"
|
||||
target_resource_id = azurerm_key_vault.keyvault.id
|
||||
log_analytics_workspace_id = var.workspace_id
|
||||
|
||||
log {
|
||||
category = "AuditEvent"
|
||||
enabled = true
|
||||
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
metric {
|
||||
category = "AllMetrics"
|
||||
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
output "id" {
|
||||
value = azurerm_key_vault.keyvault.id
|
||||
}
|
||||
|
||||
output "url" {
|
||||
value = azurerm_key_vault.keyvault.vault_uri
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "Region this module and resources will be created in"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Unique name for the services in this module"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
type = string
|
||||
description = "Environment these resources reside (prod, dev, staging, etc)"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
type = string
|
||||
description = "Owner of this environment"
|
||||
}
|
||||
|
||||
variable "tenant_id" {
|
||||
type = string
|
||||
description = "The Tenant ID"
|
||||
}
|
||||
|
||||
variable "principal_id" {
|
||||
type = string
|
||||
description = "The service principal_id of the k8s cluster"
|
||||
}
|
||||
|
||||
variable "admin_principals" {
|
||||
type = map
|
||||
description = "A list of user principals who need access to manage the keyvault"
|
||||
}
|
||||
|
||||
variable "subnet_ids" {
|
||||
description = "List of subnet_ids that will have access to this service"
|
||||
type = list
|
||||
}
|
||||
|
||||
variable "policy" {
|
||||
description = "The default policy for the network access rules (Allow/Deny)"
|
||||
default = "Deny"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "whitelist" {
|
||||
type = map
|
||||
description = "A map of whitelisted IPs and CIDR ranges. For single IPs, Azure expects just the IP, NOT a /32."
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "workspace_id" {
|
||||
description = "Log Analytics Workspace ID for sending logs generated by this resource"
|
||||
type = string
|
||||
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
resource "azurerm_resource_group" "lb" {
|
||||
name = "${var.name}-${var.environment}-lb"
|
||||
location = var.region
|
||||
}
|
||||
|
||||
resource "azurerm_public_ip" "lb" {
|
||||
name = "${var.name}-${var.environment}-ip"
|
||||
location = var.region
|
||||
resource_group_name = azurerm_resource_group.lb.name
|
||||
allocation_method = "Static"
|
||||
}
|
||||
|
||||
resource "azurerm_lb" "lb" {
|
||||
name = "${var.name}-${var.environment}-lb"
|
||||
location = var.region
|
||||
resource_group_name = azurerm_resource_group.lb.name
|
||||
|
||||
frontend_ip_configuration {
|
||||
name = "${var.name}-${var.environment}-ip"
|
||||
public_ip_address_id = azurerm_public_ip.lb.id
|
||||
}
|
||||
|
||||
tags = {
|
||||
owner = var.owner
|
||||
environment = var.environment
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "Region this module and resources will be created in"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Unique name for the services in this module"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
type = string
|
||||
description = "Environment these resources reside (prod, dev, staging, etc)"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
type = string
|
||||
description = "Owner of the environment and resources created in this module"
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
resource "azurerm_resource_group" "log_workspace" {
|
||||
name = "${var.name}-${var.environment}-log-workspace"
|
||||
location = var.region
|
||||
}
|
||||
|
||||
resource "azurerm_log_analytics_workspace" "log_workspace" {
|
||||
name = "${var.name}-${var.environment}-log-workspace"
|
||||
location = azurerm_resource_group.log_workspace.location
|
||||
resource_group_name = azurerm_resource_group.log_workspace.name
|
||||
sku = "Premium"
|
||||
tags = {
|
||||
environment = var.environment
|
||||
owner = var.owner
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
output "workspace_id" {
|
||||
value = azurerm_log_analytics_workspace.log_workspace.id
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "Region this module and resources will be created in"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Unique name for the services in this module"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
type = string
|
||||
description = "Environment these resources reside (prod, dev, staging, etc)"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
type = string
|
||||
description = "Owner of the environment and resources created in this module"
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
resource "azurerm_resource_group" "identity" {
|
||||
name = "${var.name}-${var.environment}-${var.identity}"
|
||||
location = var.region
|
||||
}
|
||||
|
||||
resource "azurerm_user_assigned_identity" "identity" {
|
||||
resource_group_name = azurerm_resource_group.identity.name
|
||||
location = azurerm_resource_group.identity.location
|
||||
|
||||
name = "${var.name}-${var.environment}-${var.identity}"
|
||||
}
|
||||
|
||||
data "azurerm_subscription" "primary" {}
|
||||
|
||||
resource "azurerm_role_assignment" "roles" {
|
||||
count = length(var.roles)
|
||||
scope = data.azurerm_subscription.primary.id
|
||||
role_definition_name = var.roles[count.index]
|
||||
principal_id = azurerm_user_assigned_identity.identity.principal_id
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
output "id" {
|
||||
value = azurerm_user_assigned_identity.identity.id
|
||||
}
|
||||
|
||||
output "principal_id" {
|
||||
value = azurerm_user_assigned_identity.identity.principal_id
|
||||
}
|
||||
|
||||
output "client_id" {
|
||||
value = azurerm_user_assigned_identity.identity.client_id
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "Region this module and resources will be created in"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Unique name for the services in this module"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
type = string
|
||||
description = "Environment these resources reside (prod, dev, staging, etc)"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
type = string
|
||||
description = "Owner of the environment and resources created in this module"
|
||||
}
|
||||
|
||||
variable "identity" {
|
||||
type = string
|
||||
description = "Name of the managed identity to create"
|
||||
}
|
||||
|
||||
variable "roles" {
|
||||
type = list
|
||||
description = "List of roles by name"
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
resource "azurerm_resource_group" "sql" {
|
||||
name = "${var.name}-${var.environment}-postgres"
|
||||
location = var.region
|
||||
}
|
||||
|
||||
resource "azurerm_postgresql_server" "sql" {
|
||||
name = "${var.name}-${var.environment}-sql"
|
||||
location = azurerm_resource_group.sql.location
|
||||
resource_group_name = azurerm_resource_group.sql.name
|
||||
|
||||
sku {
|
||||
name = var.sku_name
|
||||
capacity = var.sku_capacity
|
||||
tier = var.sku_tier
|
||||
family = var.sku_family
|
||||
}
|
||||
|
||||
storage_profile {
|
||||
storage_mb = var.storage_mb
|
||||
backup_retention_days = var.storage_backup_retention_days
|
||||
geo_redundant_backup = var.storage_geo_redundant_backup
|
||||
auto_grow = var.storage_auto_grow
|
||||
}
|
||||
|
||||
administrator_login = var.administrator_login
|
||||
administrator_login_password = var.administrator_login_password
|
||||
version = var.postgres_version
|
||||
ssl_enforcement = var.ssl_enforcement
|
||||
}
|
||||
|
||||
resource "azurerm_postgresql_virtual_network_rule" "sql" {
|
||||
name = "${var.name}-${var.environment}-rule"
|
||||
resource_group_name = azurerm_resource_group.sql.name
|
||||
server_name = azurerm_postgresql_server.sql.name
|
||||
subnet_id = var.subnet_id
|
||||
ignore_missing_vnet_service_endpoint = true
|
||||
}
|
||||
|
||||
resource "azurerm_postgresql_database" "db" {
|
||||
name = "${var.name}-${var.environment}-atat"
|
||||
resource_group_name = azurerm_resource_group.sql.name
|
||||
server_name = azurerm_postgresql_server.sql.name
|
||||
charset = "UTF8"
|
||||
collation = "en-US"
|
||||
}
|
||||
|
||||
resource "azurerm_monitor_diagnostic_setting" "postgresql_diagnostic" {
|
||||
name = "${var.name}-${var.environment}-postgresql-diag"
|
||||
target_resource_id = azurerm_postgresql_server.sql.id
|
||||
log_analytics_workspace_id = var.workspace_id
|
||||
|
||||
log {
|
||||
category = "PostgreSQLLogs"
|
||||
enabled = true
|
||||
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
metric {
|
||||
category = "AllMetrics"
|
||||
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "Region this module and resources will be created in"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Unique name for the services in this module"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
type = string
|
||||
description = "Environment these resources reside (prod, dev, staging, etc)"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
type = string
|
||||
description = "Owner of the environment and resources created in this module"
|
||||
}
|
||||
|
||||
variable "subnet_id" {
|
||||
type = string
|
||||
description = "Subnet the SQL server should run"
|
||||
}
|
||||
|
||||
variable "sku_name" {
|
||||
type = string
|
||||
description = "SKU name"
|
||||
default = "GP_Gen5_2"
|
||||
}
|
||||
|
||||
variable "sku_capacity" {
|
||||
type = string
|
||||
description = "SKU Capacity"
|
||||
default = "2"
|
||||
}
|
||||
|
||||
variable "sku_tier" {
|
||||
type = string
|
||||
description = "SKU Tier"
|
||||
default = "GeneralPurpose"
|
||||
|
||||
}
|
||||
|
||||
variable "sku_family" {
|
||||
type = string
|
||||
description = "SKU Family"
|
||||
default = "Gen5"
|
||||
}
|
||||
|
||||
variable "storage_mb" {
|
||||
type = string
|
||||
description = "Size in MB of the storage used for the sql server"
|
||||
default = "5120"
|
||||
}
|
||||
|
||||
variable "storage_backup_retention_days" {
|
||||
type = string
|
||||
description = "Storage backup retention (days)"
|
||||
default = "7"
|
||||
}
|
||||
|
||||
variable "storage_geo_redundant_backup" {
|
||||
type = string
|
||||
description = "Geographic redundant backup (Enabled/Disabled)"
|
||||
default = "Disabled"
|
||||
}
|
||||
|
||||
variable "storage_auto_grow" {
|
||||
type = string
|
||||
description = "Auto Grow? (Enabled/Disabled)"
|
||||
default = "Enabled"
|
||||
}
|
||||
|
||||
variable "administrator_login" {
|
||||
type = string
|
||||
description = "Administrator login"
|
||||
}
|
||||
|
||||
variable "administrator_login_password" {
|
||||
type = string
|
||||
description = "Administrator password"
|
||||
}
|
||||
|
||||
variable "postgres_version" {
|
||||
type = string
|
||||
description = "Postgres version to use"
|
||||
default = "10"
|
||||
}
|
||||
|
||||
variable "ssl_enforcement" {
|
||||
type = string
|
||||
description = "Enforce SSL (Enabled/Disable)"
|
||||
default = "Enabled"
|
||||
}
|
||||
|
||||
variable "workspace_id" {
|
||||
description = "Log Analytics workspace for this resource to log to"
|
||||
type = string
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
resource "azurerm_resource_group" "redis" {
|
||||
name = "${var.name}-${var.environment}-redis"
|
||||
location = var.region
|
||||
}
|
||||
|
||||
# NOTE: the Name used for Redis needs to be globally unique
|
||||
resource "azurerm_redis_cache" "redis" {
|
||||
name = "${var.name}-${var.environment}-redis"
|
||||
location = azurerm_resource_group.redis.location
|
||||
resource_group_name = azurerm_resource_group.redis.name
|
||||
capacity = var.capacity
|
||||
family = var.family
|
||||
sku_name = var.sku_name
|
||||
enable_non_ssl_port = var.enable_non_ssl_port
|
||||
minimum_tls_version = var.minimum_tls_version
|
||||
subnet_id = var.subnet_id
|
||||
|
||||
redis_configuration {
|
||||
enable_authentication = var.enable_authentication
|
||||
}
|
||||
tags = {
|
||||
environment = var.environment
|
||||
owner = var.owner
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_monitor_diagnostic_setting" "redis_diagnostic" {
|
||||
name = "${var.name}-${var.environment}-redis-diag"
|
||||
target_resource_id = azurerm_redis_cache.redis.id
|
||||
log_analytics_workspace_id = var.workspace_id
|
||||
metric {
|
||||
category = "AllMetrics"
|
||||
|
||||
retention_policy {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "Region this module and resources will be created in"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Unique name for the services in this module"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
type = string
|
||||
description = "Environment these resources reside (prod, dev, staging, etc)"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
type = string
|
||||
description = "Owner of the environment and resources created in this module"
|
||||
}
|
||||
|
||||
variable "capacity" {
|
||||
type = string
|
||||
default = 2
|
||||
description = "The capacity of the redis cache"
|
||||
}
|
||||
|
||||
variable "family" {
|
||||
type = string
|
||||
default = "C"
|
||||
description = "The subscription family for redis"
|
||||
}
|
||||
|
||||
variable "sku_name" {
|
||||
type = string
|
||||
default = "Standard"
|
||||
description = "The sku to use"
|
||||
}
|
||||
|
||||
variable "enable_non_ssl_port" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable non TLS port (default: false)"
|
||||
}
|
||||
|
||||
variable "minimum_tls_version" {
|
||||
type = string
|
||||
default = "1.2"
|
||||
description = "Minimum TLS version to use"
|
||||
}
|
||||
|
||||
variable "enable_authentication" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Enable or disable authentication (default: true)"
|
||||
}
|
||||
|
||||
variable "subnet_id" {
|
||||
type = string
|
||||
description = "Subnet ID that the service_endpoint should reside"
|
||||
}
|
||||
|
||||
variable "workspace_id" {
|
||||
description = "Log Analytics workspace for this resource to log to"
|
||||
type = string
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
resource "azurerm_resource_group" "vpc" {
|
||||
name = "${var.name}-${var.environment}-vpc"
|
||||
location = var.region
|
||||
|
||||
tags = {
|
||||
environment = var.environment
|
||||
owner = var.owner
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_network_ddos_protection_plan" "vpc" {
|
||||
count = var.ddos_enabled
|
||||
name = "${var.name}-${var.environment}-ddos"
|
||||
location = azurerm_resource_group.vpc.location
|
||||
resource_group_name = azurerm_resource_group.vpc.name
|
||||
}
|
||||
|
||||
resource "azurerm_virtual_network" "vpc" {
|
||||
name = "${var.name}-${var.environment}-network"
|
||||
location = azurerm_resource_group.vpc.location
|
||||
resource_group_name = azurerm_resource_group.vpc.name
|
||||
address_space = ["${var.virtual_network}"]
|
||||
dns_servers = var.dns_servers
|
||||
|
||||
tags = {
|
||||
environment = var.environment
|
||||
owner = var.owner
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_subnet" "subnet" {
|
||||
for_each = var.networks
|
||||
name = "${var.name}-${var.environment}-${each.key}"
|
||||
resource_group_name = azurerm_resource_group.vpc.name
|
||||
virtual_network_name = azurerm_virtual_network.vpc.name
|
||||
address_prefix = element(split(",", each.value), 0)
|
||||
|
||||
# See https://github.com/terraform-providers/terraform-provider-azurerm/issues/3471
|
||||
lifecycle {
|
||||
ignore_changes = [route_table_id]
|
||||
}
|
||||
|
||||
service_endpoints = split(",", var.service_endpoints[each.key])
|
||||
#delegation {
|
||||
# name = "acctestdelegation"
|
||||
#
|
||||
# service_delegation {
|
||||
# name = "Microsoft.ContainerInstance/containerGroups"
|
||||
# actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
|
||||
# }
|
||||
#}
|
||||
}
|
||||
|
||||
resource "azurerm_route_table" "route_table" {
|
||||
for_each = var.route_tables
|
||||
name = "${var.name}-${var.environment}-${each.key}"
|
||||
location = azurerm_resource_group.vpc.location
|
||||
resource_group_name = azurerm_resource_group.vpc.name
|
||||
}
|
||||
|
||||
resource "azurerm_subnet_route_table_association" "route_table" {
|
||||
for_each = var.networks
|
||||
subnet_id = azurerm_subnet.subnet[each.key].id
|
||||
route_table_id = azurerm_route_table.route_table[each.key].id
|
||||
}
|
||||
|
||||
resource "azurerm_route" "route" {
|
||||
for_each = var.route_tables
|
||||
name = "${var.name}-${var.environment}-default"
|
||||
resource_group_name = azurerm_resource_group.vpc.name
|
||||
route_table_name = azurerm_route_table.route_table[each.key].name
|
||||
address_prefix = "0.0.0.0/0"
|
||||
next_hop_type = each.value
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
output "subnets" {
|
||||
value = azurerm_subnet.subnet["private"].id #FIXED: this is now legacy, use subnet_list
|
||||
}
|
||||
|
||||
output "subnet_list" {
|
||||
value = {
|
||||
for k, id in azurerm_subnet.subnet : k => id
|
||||
}
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = azurerm_virtual_network.vpc.id
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
variable "environment" {
|
||||
description = "Environment (Prod,Dev,etc)"
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "Region (useast2, etc)"
|
||||
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
description = "Name or prefix to use for all resources created by this module"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
description = "Owner of these resources"
|
||||
|
||||
}
|
||||
|
||||
variable "ddos_enabled" {
|
||||
description = "Enable or disable DDoS Protection (1,0)"
|
||||
default = "0"
|
||||
}
|
||||
|
||||
variable "virtual_network" {
|
||||
description = "The supernet used for this VPC a.k.a Virtual Network"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "A map of lists describing the network topology"
|
||||
type = map
|
||||
}
|
||||
|
||||
variable "dns_servers" {
|
||||
description = "DNS Server IPs for internal and public DNS lookups (must be on a defined subnet)"
|
||||
type = list
|
||||
}
|
||||
|
||||
variable "route_tables" {
|
||||
type = map
|
||||
description = "A map with the route tables to create"
|
||||
}
|
||||
|
||||
variable "service_endpoints" {
|
||||
type = map
|
||||
description = "A map of the service endpoints and its mapping to subnets"
|
||||
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
# Task order bucket is required to be accessible publicly by the users.
|
||||
# which is why the policy here is "Allow"
|
||||
module "task_order_bucket" {
|
||||
source = "../../modules/bucket"
|
||||
service_name = "jeditasksatat"
|
||||
owner = var.owner
|
||||
name = var.name
|
||||
environment = var.environment
|
||||
region = var.region
|
||||
policy = "Allow"
|
||||
subnet_ids = [module.vpc.subnets]
|
||||
whitelist = var.storage_admin_whitelist
|
||||
}
|
||||
|
||||
# TF State should be restricted to admins only, but IP protected
|
||||
# This has to be public due to a chicken/egg issue of VPN not
|
||||
# existing until TF is run. If this bucket is private, you would
|
||||
# not be able to access it when running TF without being on a VPN.
|
||||
module "tf_state" {
|
||||
source = "../../modules/bucket"
|
||||
service_name = "jedidevtfstate"
|
||||
owner = var.owner
|
||||
name = var.name
|
||||
environment = var.environment
|
||||
region = var.region
|
||||
policy = "Deny"
|
||||
subnet_ids = []
|
||||
whitelist = var.storage_admin_whitelist
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
module "cdn" {
|
||||
source = "../../modules/cdn"
|
||||
origin_host_name = "staging.atat.code.mil"
|
||||
owner = var.owner
|
||||
environment = var.environment
|
||||
name = var.name
|
||||
region = var.region
|
||||
workspace_id = module.logs.workspace_id
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
module "container_registry" {
|
||||
source = "../../modules/container_registry"
|
||||
name = var.name
|
||||
region = var.region
|
||||
environment = var.environment
|
||||
owner = var.owner
|
||||
backup_region = var.backup_region
|
||||
policy = "Deny"
|
||||
subnet_ids = [module.vpc.subnet_list["private"].id]
|
||||
whitelist = var.admin_user_whitelist
|
||||
workspace_id = module.logs.workspace_id
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 85 KiB |
Binary file not shown.
Before Width: | Height: | Size: 71 KiB |
@ -1,50 +0,0 @@
|
||||
@startuml USEAST Development Network
|
||||
|
||||
title USEAST Development Network
|
||||
|
||||
cloud Internet
|
||||
|
||||
cloud Azure {
|
||||
[Azure Storage] as storage
|
||||
[Azure CDN] as cdn
|
||||
cdn --> storage : "HTTPS/443"
|
||||
note as cdn_note
|
||||
CDN and Azure storage are
|
||||
managed by Azure and configured
|
||||
for geographic failover
|
||||
end note
|
||||
}
|
||||
frame "USEAST Virtual Network" as vnet {
|
||||
frame "Public Route Table" as public_rt{
|
||||
frame "Public Subnet" as public_subnet {
|
||||
[ALB]
|
||||
[Internet] --> ALB
|
||||
note as public_useast
|
||||
10.1.1.0/24
|
||||
end note
|
||||
}
|
||||
}
|
||||
frame "Private Route Table" as private_rt{
|
||||
frame "Private Subnet" as private_subnet {
|
||||
[AKS]
|
||||
[Redis]
|
||||
[Postgres]
|
||||
[AzurePrivateStorage]
|
||||
AKS --> Redis : "TLS:6379"
|
||||
AKS --> Postgres : "TLS:5432"
|
||||
AKS --> AzurePrivateStorage : "HTTPS/443"
|
||||
[ALB] --> AKS : "HTTPS:443"
|
||||
note as private_useast
|
||||
10.1.2.0/24
|
||||
end note
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frame "US West Backup Region" as backupregion {
|
||||
component "Backup Postgres" as pgbackup
|
||||
[Postgres] --> pgbackup : "Private Peering / TLS:5432"
|
||||
}
|
||||
|
||||
note right of [ALB] : Azure Load Balancer restricted to AKS only
|
||||
@enduml
|
@ -1,40 +0,0 @@
|
||||
@startuml USWEST Development Network
|
||||
|
||||
title USWEST Development Network
|
||||
|
||||
cloud Internet
|
||||
|
||||
frame "USEAST Virtual Network" as vnet {
|
||||
frame "Public Route Table" as public_rt{
|
||||
frame "Public Subnet" as public_subnet {
|
||||
[ALB]
|
||||
[Internet] --> ALB
|
||||
note as public_useast
|
||||
10.2.1.0/24
|
||||
end note
|
||||
}
|
||||
}
|
||||
frame "Private Route Table" as private_rt{
|
||||
frame "Private Subnet" as private_subnet {
|
||||
[AKS]
|
||||
[Redis]
|
||||
[Postgres]
|
||||
[AzurePrivateStorage]
|
||||
AKS --> Redis : "TLS:6379"
|
||||
AKS --> Postgres : "TLS:5432"
|
||||
AKS --> AzurePrivateStorage : "HTTPS/443"
|
||||
[ALB] --> AKS : "HTTPS:443"
|
||||
note as private_useast
|
||||
10.2.2.0/24
|
||||
end note
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frame "USEAST Primary Region " as primary_region{
|
||||
component "Postgres" as pgbackup
|
||||
[Postgres] --> pgbackup : "Private Peering / TLS:5432"
|
||||
}
|
||||
|
||||
note right of [ALB] : Azure Load Balancer restricted to AKS only
|
||||
@enduml
|
@ -1,10 +0,0 @@
|
||||
module "keyvault_reader_identity" {
|
||||
source = "../../modules/managed_identity"
|
||||
name = var.name
|
||||
owner = var.owner
|
||||
environment = var.environment
|
||||
region = var.region
|
||||
identity = "${var.name}-${var.environment}-vault-reader"
|
||||
roles = ["Reader", "Managed Identity Operator"]
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
data "azurerm_key_vault_secret" "k8s_client_id" {
|
||||
name = "k8s-client-id"
|
||||
key_vault_id = module.operator_keyvault.id
|
||||
}
|
||||
|
||||
data "azurerm_key_vault_secret" "k8s_client_secret" {
|
||||
name = "k8s-client-secret"
|
||||
key_vault_id = module.operator_keyvault.id
|
||||
}
|
||||
|
||||
module "k8s" {
|
||||
source = "../../modules/k8s"
|
||||
region = var.region
|
||||
name = var.name
|
||||
environment = var.environment
|
||||
owner = var.owner
|
||||
k8s_dns_prefix = var.k8s_dns_prefix
|
||||
k8s_node_size = var.k8s_node_size
|
||||
vnet_subnet_id = module.vpc.subnets #FIXME - output from module.vpc.subnets should be map
|
||||
enable_auto_scaling = true
|
||||
max_count = 5
|
||||
min_count = 3
|
||||
client_id = data.azurerm_key_vault_secret.k8s_client_id.value
|
||||
client_secret = data.azurerm_key_vault_secret.k8s_client_secret.value
|
||||
workspace_id = module.logs.workspace_id
|
||||
vnet_id = module.vpc.id
|
||||
}
|
||||
|
||||
#module "main_lb" {
|
||||
# source = "../../modules/lb"
|
||||
# region = var.region
|
||||
# name = "main-${var.name}"
|
||||
# environment = var.environment
|
||||
# owner = var.owner
|
||||
#}
|
||||
|
||||
#module "auth_lb" {
|
||||
# source = "../../modules/lb"
|
||||
# region = var.region
|
||||
# name = "auth-${var.name}"
|
||||
# environment = var.environment
|
||||
# owner = var.owner
|
||||
#}
|
@ -1,15 +0,0 @@
|
||||
module "keyvault" {
|
||||
source = "../../modules/keyvault"
|
||||
name = "cz"
|
||||
region = var.region
|
||||
owner = var.owner
|
||||
environment = var.environment
|
||||
tenant_id = var.tenant_id
|
||||
principal_id = "f9bcbe58-8b73-4957-aee2-133dc3e58063"
|
||||
admin_principals = var.admin_users
|
||||
policy = "Deny"
|
||||
subnet_ids = [module.vpc.subnets]
|
||||
whitelist = var.admin_user_whitelist
|
||||
workspace_id = module.logs.workspace_id
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
module "logs" {
|
||||
source = "../../modules/log_analytics"
|
||||
owner = var.owner
|
||||
environment = var.environment
|
||||
region = var.region
|
||||
name = var.name
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
data "azurerm_key_vault_secret" "postgres_username" {
|
||||
name = "postgres-root-user"
|
||||
key_vault_id = module.operator_keyvault.id
|
||||
}
|
||||
|
||||
data "azurerm_key_vault_secret" "postgres_password" {
|
||||
name = "postgres-root-password"
|
||||
key_vault_id = module.operator_keyvault.id
|
||||
}
|
||||
|
||||
module "sql" {
|
||||
source = "../../modules/postgres"
|
||||
name = var.name
|
||||
owner = var.owner
|
||||
environment = var.environment
|
||||
region = var.region
|
||||
subnet_id = module.vpc.subnet_list["private"].id
|
||||
administrator_login = data.azurerm_key_vault_secret.postgres_username.value
|
||||
administrator_login_password = data.azurerm_key_vault_secret.postgres_password.value
|
||||
workspace_id = module.logs.workspace_id
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
provider "azurerm" {
|
||||
version = "=1.40.0"
|
||||
}
|
||||
|
||||
provider "azuread" {
|
||||
# Whilst version is optional, we /strongly recommend/ using it to pin the version of the Provider being used
|
||||
version = "=0.7.0"
|
||||
}
|
||||
|
||||
terraform {
|
||||
backend "azurerm" {
|
||||
resource_group_name = "cloudzero-jedidev-jedidevtfstate"
|
||||
storage_account_name = "jedidevtfstate"
|
||||
container_name = "tfstate"
|
||||
key = "dev.terraform.tfstate"
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
module "redis" {
|
||||
source = "../../modules/redis"
|
||||
owner = var.owner
|
||||
environment = var.environment
|
||||
region = var.region
|
||||
name = var.name
|
||||
subnet_id = module.vpc.subnet_list["redis"].id
|
||||
sku_name = "Premium"
|
||||
family = "P"
|
||||
workspace_id = module.logs.workspace_id
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
module "operator_keyvault" {
|
||||
source = "../../modules/keyvault"
|
||||
name = "ops"
|
||||
region = var.region
|
||||
owner = var.owner
|
||||
environment = var.environment
|
||||
tenant_id = var.tenant_id
|
||||
principal_id = ""
|
||||
admin_principals = var.admin_users
|
||||
policy = "Deny"
|
||||
subnet_ids = [module.vpc.subnets]
|
||||
whitelist = var.admin_user_whitelist
|
||||
workspace_id = module.logs.workspace_id
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
variable "environment" {
|
||||
default = "jedidev"
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
default = "eastus"
|
||||
|
||||
}
|
||||
|
||||
variable "backup_region" {
|
||||
default = "westus2"
|
||||
}
|
||||
|
||||
|
||||
variable "owner" {
|
||||
default = "dev"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
default = "cloudzero"
|
||||
}
|
||||
|
||||
variable "virtual_network" {
|
||||
type = string
|
||||
default = "10.1.0.0/16"
|
||||
}
|
||||
|
||||
|
||||
variable "networks" {
|
||||
type = map
|
||||
default = {
|
||||
#format
|
||||
#name = "CIDR, route table, Security Group Name"
|
||||
public = "10.1.1.0/24,public" # LBs
|
||||
private = "10.1.2.0/24,private" # k8s, postgres, keyvault
|
||||
redis = "10.1.3.0/24,private" # Redis
|
||||
apps = "10.1.4.0/24,private" # Redis
|
||||
}
|
||||
}
|
||||
|
||||
variable "service_endpoints" {
|
||||
type = map
|
||||
default = {
|
||||
public = "Microsoft.ContainerRegistry" # Not necessary but added to avoid infinite state loop
|
||||
private = "Microsoft.Storage,Microsoft.KeyVault,Microsoft.ContainerRegistry,Microsoft.Sql"
|
||||
redis = "Microsoft.Storage,Microsoft.Sql" # FIXME: There is no Microsoft.Redis
|
||||
apps = "Microsoft.Storage,Microsoft.KeyVault,Microsoft.ContainerRegistry,Microsoft.Sql"
|
||||
}
|
||||
}
|
||||
|
||||
variable "route_tables" {
|
||||
description = "Route tables and their default routes"
|
||||
type = map
|
||||
default = {
|
||||
public = "Internet"
|
||||
private = "Internet" # TODO: Switch to FW
|
||||
redis = "VnetLocal"
|
||||
apps = "Internet" # TODO: Switch to FW
|
||||
}
|
||||
}
|
||||
|
||||
variable "dns_servers" {
|
||||
type = list
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "k8s_node_size" {
|
||||
type = string
|
||||
default = "Standard_A1_v2"
|
||||
}
|
||||
|
||||
variable "k8s_dns_prefix" {
|
||||
type = string
|
||||
default = "atat"
|
||||
}
|
||||
|
||||
variable "tenant_id" {
|
||||
type = string
|
||||
default = "47f616e9-6ff5-4736-9b9e-b3f62c93a915"
|
||||
}
|
||||
|
||||
variable "admin_users" {
|
||||
type = map
|
||||
default = {
|
||||
"Rob Gil" = "cef37d01-1acf-4085-96c8-da9d34d0237e"
|
||||
"Dan Corrigan" = "7e852ceb-eb0d-49b1-b71e-e9dcd1082ffc"
|
||||
}
|
||||
}
|
||||
|
||||
variable "admin_user_whitelist" {
|
||||
type = map
|
||||
default = {
|
||||
"Rob Gil" = "66.220.238.246/32"
|
||||
"Dan Corrigan Work" = "108.16.207.173/32"
|
||||
"Dan Corrigan Home" = "71.162.221.27/32"
|
||||
}
|
||||
}
|
||||
|
||||
variable "storage_admin_whitelist" {
|
||||
type = map
|
||||
default = {
|
||||
"Rob Gil" = "66.220.238.246"
|
||||
"Dan Corrigan Work" = "108.16.207.173"
|
||||
"Dan Corrigan Home" = "71.162.221.27"
|
||||
}
|
||||
}
|
||||
|
||||
variable "vpn_client_cidr" {
|
||||
type = list
|
||||
default = ["172.16.255.0/24"]
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
module "vpc" {
|
||||
source = "../../modules/vpc/"
|
||||
environment = var.environment
|
||||
region = var.region
|
||||
virtual_network = var.virtual_network
|
||||
networks = var.networks
|
||||
route_tables = var.route_tables
|
||||
owner = var.owner
|
||||
name = var.name
|
||||
dns_servers = var.dns_servers
|
||||
service_endpoints = var.service_endpoints
|
||||
}
|
4
terraform/secrets-tool/.gitignore
vendored
4
terraform/secrets-tool/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
bin/
|
||||
include/
|
||||
lib/
|
||||
|
@ -1,63 +0,0 @@
|
||||
[[source]]
|
||||
url = "https://pypi.python.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
requests = "==2.22.0"
|
||||
adal = "==1.2.2"
|
||||
antlr4-python3-runtime = "==4.7.2"
|
||||
applicationinsights = "==0.11.9"
|
||||
argcomplete = "==1.10.3"
|
||||
astroid = "==2.3.3"
|
||||
azure-cli-core = "==2.0.77"
|
||||
azure-cli-nspkg = "==3.0.4"
|
||||
azure-cli-telemetry = "==1.0.4"
|
||||
azure-common = "==1.1.23"
|
||||
azure-core = "==1.1.1"
|
||||
azure-identity = "==1.1.0"
|
||||
azure-keyvault = "==4.0.0"
|
||||
azure-keyvault-keys = "==4.0.0"
|
||||
azure-keyvault-secrets = "==4.0.0"
|
||||
azure-mgmt-resource = "==4.0.0"
|
||||
azure-nspkg = "==3.0.2"
|
||||
bcrypt = "==3.1.7"
|
||||
certifi = "==2019.11.28"
|
||||
cffi = "==1.13.2"
|
||||
chardet = "==3.0.4"
|
||||
click = "==7.0"
|
||||
colorama = "==0.4.3"
|
||||
coloredlogs = "==10.0"
|
||||
cryptography = "==2.8"
|
||||
humanfriendly = "==4.18"
|
||||
idna = "==2.8"
|
||||
isodate = "==0.6.0"
|
||||
isort = "==4.3.21"
|
||||
jmespath = "==0.9.4"
|
||||
knack = "==0.6.3"
|
||||
lazy-object-proxy = "==1.4.3"
|
||||
mccabe = "==0.6.1"
|
||||
msal = "==1.0.0"
|
||||
msal-extensions = "==0.1.3"
|
||||
msrest = "==0.6.10"
|
||||
msrestazure = "==0.6.2"
|
||||
oauthlib = "==3.1.0"
|
||||
paramiko = "==2.7.1"
|
||||
portalocker = "==1.5.2"
|
||||
pycparser = "==2.19"
|
||||
Pygments = "==2.5.2"
|
||||
PyJWT = "==1.7.1"
|
||||
pylint = "==2.4.4"
|
||||
PyNaCl = "==1.3.0"
|
||||
pyOpenSSL = "==19.1.0"
|
||||
python-dateutil = "==2.8.1"
|
||||
PyYAML = "==5.2"
|
||||
requests-oauthlib = "==1.3.0"
|
||||
six = "==1.13.0"
|
||||
tabulate = "==0.8.6"
|
||||
typed-ast = "==1.4.0"
|
||||
urllib3 = "==1.25.7"
|
||||
wrapt = "==1.11.2"
|
||||
|
||||
[dev-packages]
|
||||
pytest = "*"
|
670
terraform/secrets-tool/Pipfile.lock
generated
670
terraform/secrets-tool/Pipfile.lock
generated
@ -1,670 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "83c0cc35bbf74c0b7620a5b7f4f9fab4324e86442e12284d1b9ffcc12a371696"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"adal": {
|
||||
"hashes": [
|
||||
"sha256:5a7f1e037c6290c6d7609cab33a9e5e988c2fbec5c51d1c4c649ee3faff37eaf",
|
||||
"sha256:fd17e5661f60634ddf96a569b95d34ccb8a98de60593d729c28bdcfe360eaad1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.2"
|
||||
},
|
||||
"antlr4-python3-runtime": {
|
||||
"hashes": [
|
||||
"sha256:168cdcec8fb9152e84a87ca6fd261b3d54c8f6358f42ab3b813b14a7193bb50b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.0'",
|
||||
"version": "==4.7.2"
|
||||
},
|
||||
"applicationinsights": {
|
||||
"hashes": [
|
||||
"sha256:30a11aafacea34f8b160fbdc35254c9029c7e325267874e3c68f6bdbcd6ed2c3",
|
||||
"sha256:b88bc5a41385d8e516489128d5e63f8c52efe597a3579b1718d1ab2f7cf150a2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.11.9"
|
||||
},
|
||||
"argcomplete": {
|
||||
"hashes": [
|
||||
"sha256:a37f522cf3b6a34abddfedb61c4546f60023b3799b22d1cd971eacdc0861530a",
|
||||
"sha256:d8ea63ebaec7f59e56e7b2a386b1d1c7f1a7ae87902c9ee17d377eaa557f06fa"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.10.3"
|
||||
},
|
||||
"astroid": {
|
||||
"hashes": [
|
||||
"sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a",
|
||||
"sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.3.3"
|
||||
},
|
||||
"azure-cli-core": {
|
||||
"hashes": [
|
||||
"sha256:4281b71cf9a8278f665765c97eb3dae61fbf2dac916fc032c4acdf5ed2065210",
|
||||
"sha256:d14a733dd6d6019c23dbd0b459026fef0978901d263382a1fdb71852293b9e6a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.77"
|
||||
},
|
||||
"azure-cli-nspkg": {
|
||||
"hashes": [
|
||||
"sha256:1bde56090f548c6435bd3093995cf88e4c445fb040604df8b5b5f70780d79181",
|
||||
"sha256:9a1e4f3197183470e4afecfdd45c92320f6753555b06a70651f89972332ffaf6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"azure-cli-telemetry": {
|
||||
"hashes": [
|
||||
"sha256:1f239d544d309c29e827982cc20113eb57037dba16db6cdd2e0283e437e0e577",
|
||||
"sha256:7b18d7520e35e134136a0f7de38403a7dbce7b1e835065bd9e965579815ddf2f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.4"
|
||||
},
|
||||
"azure-common": {
|
||||
"hashes": [
|
||||
"sha256:53b1195b8f20943ccc0e71a17849258f7781bc6db1c72edc7d6c055f79bd54e3",
|
||||
"sha256:99ef36e74b6395329aada288764ce80504da16ecc8206cb9a72f55fb02e8b484"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.1.23"
|
||||
},
|
||||
"azure-core": {
|
||||
"hashes": [
|
||||
"sha256:4d047fd4e46a958c9b63f9d5cb52e6bf7dfc5c2a1c2a81b968499335a94bb5cb",
|
||||
"sha256:b44fe5b46d2bb0260cafb737ab5ee89a16d478fc1885dabe21c426c4df205502"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"azure-identity": {
|
||||
"hashes": [
|
||||
"sha256:0c8e540e1b75d48c54e5cd8d599f7ea5ccf4dae260c35bebb0c8d34d22b7c4f6",
|
||||
"sha256:75f4ad9abfd191bd5f3de4c6dc29980b138bf5dfbbef9bca5c548a6048473bde"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"azure-keyvault": {
|
||||
"hashes": [
|
||||
"sha256:76f75cb83929f312a08616d426ad6f597f1beae180131cf445876fb88f2c8ef1",
|
||||
"sha256:e85f5bd6cb4f10b3248b99bbf02e3acc6371d366846897027d4153f18025a2d7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"azure-keyvault-keys": {
|
||||
"hashes": [
|
||||
"sha256:2983fa42e20a0e6bf6b87976716129c108e613e0292d34c5b0f0c8dc1d488e89",
|
||||
"sha256:38c27322637a2c52620a8b96da1942ad6a8d22d09b5a01f6fa257f7a51e52ed0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"azure-keyvault-secrets": {
|
||||
"hashes": [
|
||||
"sha256:2eae9264a8f6f59277e1a9bfdbc8b0a15969ee5a80d8efe403d7744805b4a481",
|
||||
"sha256:97a602406a833e8f117c540c66059c818f4321a35168dd17365fab1e4527d718"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"azure-mgmt-resource": {
|
||||
"hashes": [
|
||||
"sha256:2b909f137469c7bfa541554c3d22eb918e9191c07667a42f2c6fc684e24ac83f",
|
||||
"sha256:5022263349e66dba5ddadd3bf36208d82a00e0f1bb3288e32822fc821ccd1f76"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"azure-nspkg": {
|
||||
"hashes": [
|
||||
"sha256:1d0bbb2157cf57b1bef6c8c8e5b41133957364456c43b0a43599890023cca0a8",
|
||||
"sha256:31a060caca00ed1ebd369fc7fe01a56768c927e404ebc92268f4d9d636435e28",
|
||||
"sha256:e7d3cea6af63e667d87ba1ca4f8cd7cb4dfca678e4c55fc1cedb320760e39dd0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.2"
|
||||
},
|
||||
"bcrypt": {
|
||||
"hashes": [
|
||||
"sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89",
|
||||
"sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42",
|
||||
"sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294",
|
||||
"sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161",
|
||||
"sha256:6305557019906466fc42dbc53b46da004e72fd7a551c044a827e572c82191752",
|
||||
"sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31",
|
||||
"sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5",
|
||||
"sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c",
|
||||
"sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0",
|
||||
"sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de",
|
||||
"sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e",
|
||||
"sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052",
|
||||
"sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09",
|
||||
"sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105",
|
||||
"sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133",
|
||||
"sha256:ce4e4f0deb51d38b1611a27f330426154f2980e66582dc5f438aad38b5f24fc1",
|
||||
"sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7",
|
||||
"sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.7"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
|
||||
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2019.11.28"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42",
|
||||
"sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04",
|
||||
"sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5",
|
||||
"sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54",
|
||||
"sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba",
|
||||
"sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57",
|
||||
"sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396",
|
||||
"sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12",
|
||||
"sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97",
|
||||
"sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43",
|
||||
"sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db",
|
||||
"sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3",
|
||||
"sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b",
|
||||
"sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579",
|
||||
"sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346",
|
||||
"sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159",
|
||||
"sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652",
|
||||
"sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e",
|
||||
"sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a",
|
||||
"sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506",
|
||||
"sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f",
|
||||
"sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d",
|
||||
"sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c",
|
||||
"sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20",
|
||||
"sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858",
|
||||
"sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc",
|
||||
"sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a",
|
||||
"sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3",
|
||||
"sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e",
|
||||
"sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410",
|
||||
"sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25",
|
||||
"sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b",
|
||||
"sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.13.2"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==7.0"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
|
||||
"sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.4.3"
|
||||
},
|
||||
"coloredlogs": {
|
||||
"hashes": [
|
||||
"sha256:34fad2e342d5a559c31b6c889e8d14f97cb62c47d9a2ae7b5ed14ea10a79eff8",
|
||||
"sha256:b869a2dda3fa88154b9dd850e27828d8755bfab5a838a1c97fbc850c6e377c36"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==10.0"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c",
|
||||
"sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595",
|
||||
"sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad",
|
||||
"sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651",
|
||||
"sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2",
|
||||
"sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff",
|
||||
"sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d",
|
||||
"sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42",
|
||||
"sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d",
|
||||
"sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e",
|
||||
"sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912",
|
||||
"sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793",
|
||||
"sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13",
|
||||
"sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7",
|
||||
"sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0",
|
||||
"sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879",
|
||||
"sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f",
|
||||
"sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9",
|
||||
"sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2",
|
||||
"sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf",
|
||||
"sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.8"
|
||||
},
|
||||
"humanfriendly": {
|
||||
"hashes": [
|
||||
"sha256:23057b10ad6f782e7bc3a20e3cb6768ab919f619bbdc0dd75691121bbde5591d",
|
||||
"sha256:33ee8ceb63f1db61cce8b5c800c531e1a61023ac5488ccde2ba574a85be00a85"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.18"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.8"
|
||||
},
|
||||
"isodate": {
|
||||
"hashes": [
|
||||
"sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8",
|
||||
"sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
|
||||
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.21"
|
||||
},
|
||||
"jmespath": {
|
||||
"hashes": [
|
||||
"sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6",
|
||||
"sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.9.4"
|
||||
},
|
||||
"knack": {
|
||||
"hashes": [
|
||||
"sha256:b1ac92669641b902e1aef97138666a21b8852f65d83cbde03eb9ddebf82ce121",
|
||||
"sha256:bd240163d4e2ce9fc8535f77519358da0afd6c0ca19f001c639c3160b57630a9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.6.3"
|
||||
},
|
||||
"lazy-object-proxy": {
|
||||
"hashes": [
|
||||
"sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d",
|
||||
"sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449",
|
||||
"sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08",
|
||||
"sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a",
|
||||
"sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50",
|
||||
"sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd",
|
||||
"sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239",
|
||||
"sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb",
|
||||
"sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea",
|
||||
"sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e",
|
||||
"sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156",
|
||||
"sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142",
|
||||
"sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442",
|
||||
"sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62",
|
||||
"sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db",
|
||||
"sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531",
|
||||
"sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383",
|
||||
"sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a",
|
||||
"sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357",
|
||||
"sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
|
||||
"sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"msal": {
|
||||
"hashes": [
|
||||
"sha256:c944b833bf686dfbc973e9affdef94b77e616cb52ab397e76cde82e26b8a3373",
|
||||
"sha256:ecbe3f5ac77facad16abf08eb9d8562af3bc7184be5d4d90c9ef4db5bde26340"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"msal-extensions": {
|
||||
"hashes": [
|
||||
"sha256:59e171a9a4baacdbf001c66915efeaef372fb424421f1a4397115a3ddd6205dc",
|
||||
"sha256:c5a32b8e1dce1c67733dcdf8aa8bebcff5ab123e779ef7bc14e416bd0da90037"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.1.3"
|
||||
},
|
||||
"msrest": {
|
||||
"hashes": [
|
||||
"sha256:56b8b5b4556fb2a92cac640df267d560889bdc9e2921187772d4691d97bc4e8d",
|
||||
"sha256:f5153bfe60ee757725816aedaa0772cbfe0bddb52cd2d6db4cb8b4c3c6c6f928"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.6.10"
|
||||
},
|
||||
"msrestazure": {
|
||||
"hashes": [
|
||||
"sha256:63db9f646fffc9244b332090e679d1e5f283ac491ee0cc321f5116f9450deb4a",
|
||||
"sha256:fecb6a72a3eb5483e4deff38210d26ae42d3f6d488a7a275bd2423a1a014b22c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.6.2"
|
||||
},
|
||||
"oauthlib": {
|
||||
"hashes": [
|
||||
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
|
||||
"sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.0"
|
||||
},
|
||||
"paramiko": {
|
||||
"hashes": [
|
||||
"sha256:920492895db8013f6cc0179293147f830b8c7b21fdfc839b6bad760c27459d9f",
|
||||
"sha256:9c980875fa4d2cb751604664e9a2d0f69096643f5be4db1b99599fe114a97b2f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.7.1"
|
||||
},
|
||||
"portalocker": {
|
||||
"hashes": [
|
||||
"sha256:6f57aabb25ba176462dc7c63b86c42ad6a9b5bd3d679a9d776d0536bfb803d54",
|
||||
"sha256:dac62e53e5670cb40d2ee4cdc785e6b829665932c3ee75307ad677cf5f7d2e9f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.5.2"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.19"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
|
||||
"sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.5.2"
|
||||
},
|
||||
"pyjwt": {
|
||||
"hashes": [
|
||||
"sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e",
|
||||
"sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.7.1"
|
||||
},
|
||||
"pylint": {
|
||||
"hashes": [
|
||||
"sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd",
|
||||
"sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4.4"
|
||||
},
|
||||
"pynacl": {
|
||||
"hashes": [
|
||||
"sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255",
|
||||
"sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c",
|
||||
"sha256:0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e",
|
||||
"sha256:1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae",
|
||||
"sha256:2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621",
|
||||
"sha256:2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56",
|
||||
"sha256:30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39",
|
||||
"sha256:37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310",
|
||||
"sha256:4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1",
|
||||
"sha256:53126cd91356342dcae7e209f840212a58dcf1177ad52c1d938d428eebc9fee5",
|
||||
"sha256:57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a",
|
||||
"sha256:5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786",
|
||||
"sha256:6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b",
|
||||
"sha256:7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b",
|
||||
"sha256:a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f",
|
||||
"sha256:a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20",
|
||||
"sha256:aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415",
|
||||
"sha256:bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715",
|
||||
"sha256:bf459128feb543cfca16a95f8da31e2e65e4c5257d2f3dfa8c0c1031139c9c92",
|
||||
"sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1",
|
||||
"sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"pyopenssl": {
|
||||
"hashes": [
|
||||
"sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504",
|
||||
"sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==19.1.0"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.8.1"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
|
||||
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
|
||||
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
|
||||
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
|
||||
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
|
||||
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
|
||||
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
|
||||
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
|
||||
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
|
||||
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
|
||||
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.2"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
|
||||
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.22.0"
|
||||
},
|
||||
"requests-oauthlib": {
|
||||
"hashes": [
|
||||
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
|
||||
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
|
||||
"sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
|
||||
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.13.0"
|
||||
},
|
||||
"tabulate": {
|
||||
"hashes": [
|
||||
"sha256:5470cc6687a091c7042cee89b2946d9235fe9f6d49c193a4ae2ac7bf386737c8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.8.6"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161",
|
||||
"sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e",
|
||||
"sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e",
|
||||
"sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0",
|
||||
"sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c",
|
||||
"sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47",
|
||||
"sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631",
|
||||
"sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4",
|
||||
"sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34",
|
||||
"sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b",
|
||||
"sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2",
|
||||
"sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e",
|
||||
"sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a",
|
||||
"sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233",
|
||||
"sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1",
|
||||
"sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36",
|
||||
"sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d",
|
||||
"sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a",
|
||||
"sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66",
|
||||
"sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "implementation_name == 'cpython' and python_version < '3.8'",
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293",
|
||||
"sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.25.7"
|
||||
},
|
||||
"wheel": {
|
||||
"hashes": [
|
||||
"sha256:9515fe0a94e823fd90b08d22de45d7bde57c90edce705b22f5e1ecf7e1b653c8",
|
||||
"sha256:e721e53864f084f956f40f96124a74da0631ac13fbbd1ba99e8e2b5e9cafdf64"
|
||||
],
|
||||
"version": "==0.30.0"
|
||||
},
|
||||
"wrapt": {
|
||||
"hashes": [
|
||||
"sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.11.2"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||
],
|
||||
"version": "==19.3.0"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:bdd9b7c397c273bcc9a11d6629a38487cd07154fa255a467bf704cd2c258e359",
|
||||
"sha256:f17c015735e1a88296994c0697ecea7e11db24290941983b08c9feb30921e6d8"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:1a2a32c72400d365000412fe08eb4a24ebee89997c18d3d147544f70f5403b39",
|
||||
"sha256:c468adec578380b6281a114cb8a5db34eb1116277da92d7c46f904f0b52d3288"
|
||||
],
|
||||
"version": "==8.1.0"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb",
|
||||
"sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8"
|
||||
],
|
||||
"version": "==20.0"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
||||
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
||||
],
|
||||
"version": "==0.13.1"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
|
||||
"sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
|
||||
],
|
||||
"version": "==1.8.1"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
|
||||
"sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
|
||||
],
|
||||
"version": "==2.4.6"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa",
|
||||
"sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.3.2"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
|
||||
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.13.0"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
"sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603",
|
||||
"sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"
|
||||
],
|
||||
"version": "==0.1.8"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:8dda78f06bd1674bd8720df8a50bb47b6e1233c503a4eed8e7810686bde37656",
|
||||
"sha256:d38fbe01bbf7a3593a32bc35a9c4453c32bc42b98c377f9bff7e9f8da157786c"
|
||||
],
|
||||
"version": "==1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
# secrets-tool
|
||||
secrets-tool is a group of utilities used to manage secrets in Azure environments.
|
||||
|
||||
*Features:*
|
||||
- Generate secrets based on definitions defined in yaml
|
||||
- Load secrets in to Azure KeyVault
|
||||
- Wrapper for terraform to inject KeyVault secrets as environment variables
|
||||
|
||||
# Use Cases
|
||||
## Populating KeyVault with initial secrets
|
||||
In many environments, a complete list of secrets is sometimes forgotten or not well defined. With secrets-tool, all those secrets can be defined programatically and generated when creating new environments. This avoids putting in "test" values for passwords and guessible username/password combinations. Even usernames can be generated.
|
||||
|
||||
With both usernames and passwords generated, the application only needs to make a call out to KeyVault for the key that it needs (assuming the application, host, or vm has access to the secret)
|
||||
|
||||
Ex.
|
||||
```
|
||||
{
|
||||
'postgres_root_user': 'EzTEzSNLKQPHuJyPdPloIDCAlcibbl',
|
||||
'postgres_root_password': "2+[A@E4:C=ubb/#R#'n<p|wCW-|%q^" <!-- pragma: allowlist secret -->
|
||||
}
|
||||
```
|
||||
|
||||
## Rotating secrets
|
||||
Rotating passwords is a snap! Just re-run secrets-tool and it will generate and populate new secrets.
|
||||
|
||||
**Be careful!! There is no safeguard to prevent you from accidentally overwriting secrets!! - To be added if desired**
|
||||
|
||||
## Terraform Secrets
|
||||
Terraform typically expects user defined secrets to be stored in either a file, or in another service such as keyvault. The terraform wrapper feature, injects secrets from keyvault in to the environment and then runs terraform.
|
||||
|
||||
This provides a number of security benefits. First, secrets are not on disk. Secondly, users/operators never see the secrets fly by (passerbys or voyeurs that like to look over your shoulder when deploying to production)
|
||||
|
||||
## Setting up the initial ATAT database
|
||||
|
||||
This handles bootstrapping the ATAT database with a user, schema, and initial data.
|
||||
|
||||
It does the following:
|
||||
|
||||
- Sources the Postgres root user credentials
|
||||
- Source the Postgres ATAT user password
|
||||
- Runs a script inside an ATAT docker container to set up the initial database user, schema, and seed data in the database
|
||||
|
||||
Requirements:
|
||||
|
||||
- docker
|
||||
- A copy of the ATAT docker image. This can be built in the repo root with: `docker build . --build-arg CSP=azure -f ./Dockerfile -t atat:latest`
|
||||
- You need to know the hostname for the Postgres database. Your IP must either be whitelisted in its firewall rules or you must be behind the VPN.
|
||||
- You will need a YAML file listing all the CCPO users to be added to the database, with the format:
|
||||
|
||||
```
|
||||
- dod_id: "2323232323"
|
||||
first_name: "Luke"
|
||||
last_name: "Skywalker"
|
||||
- dod_id: "5656565656"
|
||||
first_name: "Han"
|
||||
last_name: "Solo"
|
||||
```
|
||||
|
||||
- There should be a password for the ATAT database user in the application Key Vault, preferably named `PGPASSWORD`. You can load this by running `secrets-tool --keyvault [operator key vault url] load -f postgres-user.yml` and supplying YAML like:
|
||||
|
||||
```
|
||||
---
|
||||
- PGPASSWORD:
|
||||
type: 'password'
|
||||
length: 30
|
||||
```
|
||||
|
||||
This command takes a lot of arguments. Run `secrets-tool database --keyvault [operator key vault url] provision -- help` to see the full list of available options.
|
||||
|
||||
The command supplies some defaults by assuming you've followed the patterns in sample-secrets.yml and elsewhere.
|
||||
|
||||
An example would be:
|
||||
|
||||
```
|
||||
secrets-tool database --keyvault [operator key vault URL] provision --app-keyvault [application key vault URL] --dbname jedidev-atat --dbhost [database host name] --ccpo-users /full/path/to/users.yml
|
||||
```
|
||||
|
||||
# Setup
|
||||
|
||||
*Requirements*
|
||||
- Python 3.7+
|
||||
- pipenv
|
||||
|
||||
```
|
||||
cd secrets-tool
|
||||
pipenv install
|
||||
pipenv shell
|
||||
```
|
||||
|
||||
You will also need to make sure secrets-tool is in your PATH
|
||||
|
||||
```
|
||||
echo 'PATH=$PATH:<path to secrets-tool>' > ~/.bash_profile
|
||||
. ~/.bash_profile
|
||||
```
|
||||
|
||||
`$ which secrets-tool` should show the full path
|
||||
|
||||
# Usage
|
||||
## Defining secrets
|
||||
The schema for defining secrets is very simplistic for the moment.
|
||||
```yaml
|
||||
---
|
||||
- postgres-root-user:
|
||||
type: 'username'
|
||||
length: 30
|
||||
- postgres-root-password:
|
||||
type: 'password'
|
||||
length: 30
|
||||
```
|
||||
In this example we're randomly generating both the username and password. `secrets-tool` is smart enough to know that a username can't have symbols in it. Passwords contain symbols, upper/lower case, and numbers. This could be made more flexible and configurable in the future.
|
||||
|
||||
|
||||
## Populating secrets from secrets definition file
|
||||
This process is as simple as specifying the keyvault and the definitions file.
|
||||
```
|
||||
secrets-tool secrets --keyvault https://operator-dev-keyvault.vault.azure.net/ load -f ./sample-secrets.yaml
|
||||
```
|
||||
|
||||
## Running terraform with KeyVault secrets
|
||||
This will fetch all secrets from the keyvault specified. `secrets-tool` then converts the keys to a variable name that terraform will look for. Essentially it prepends the keys found in KeyVault with `TF_VAR` and then executes terraform as a subprocess with the injected environment variables.
|
||||
```
|
||||
secrets-tool terraform --keyvault https://operator-dev-keyvault.vault.azure.net/ plan
|
||||
```
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user