def new_blob_client() -> BlobClient: """ Returns an instance of BlobClient that attaches to state storage blob in Azure storage account. """ return BlobClient.from_connection_string( envconfig.req("AzureWebJobsStorage"), container_name="statestorage", blob_name="state.json", )
def _get_secret_from_keyvault(self, id: str) -> typing.Union[KeyVaultSecret, None]: """ Gets a certificate from Azure Keyvault and returns an instance of KeyVaultSecret. Or if no secret is found returns None. """ logging.debug("Trying to get certificate from Azure KeyVault for '%s'", id) vault_name = envconfig.req("AZURE_KEYVAULT_NAME") secret_client = SecretClient( vault_url=f"https://{vault_name}.vault.azure.net/", credential=self.credentials, ) try: secret = secret_client.get_secret(id) return secret except ResourceNotFoundError: logging.debug("No certificate found from Azure Keyvault for '%s'", id) return None
def main(req: func.HttpRequest, msg: func.Out[str]) -> func.HttpResponse: """ Insert-Update request endpoint """ api_baseurl = envconfig.req("API_BASEURL") blob_client = st.new_blob_client() logging.debug("CLIENT ID IS %s", req.route_params.get("client_id")) # Check if JSON is valid try: req_json = req.get_json() except ValueError: return http.invalid_json_response() # Validate data try: req_body = cfg.IngressUpdate(**req_json) except pydantic.error_wrappers.ValidationError as e: return http.validation_error_response(e) ## Check status (if possible, add to queue) try: client_id = cfg.Id.from_str(req.route_params.get("client_id")) state_in_azure = st.StateInAzure(blob_client) queue_item = state_in_azure.upsert(client_id, req_body, datetime.datetime.now()) msg.set(queue_item.json()) response = { "success": True, "msg": "Added to queue.", "poll_url": f"{api_baseurl}/v1/{str(client_id)}/status", "data": json.loads(queue_item.json()), } return func.HttpResponse(json.dumps(response), status_code=202, headers=http.response_headers()) except st.UpdateConflictError as e: return http.update_conflict_response(e) except pydantic.error_wrappers.ValidationError as e: return http.validation_error_response(e)
def from_env(cls: typing.Type["Options"]) -> "Options": """ Load all options from environment variables """ return cls( azure_subscription_id=envconfig.req("AZURE_SUBSCRIPTION_ID"), azure_dns_zone_resource_group=envconfig.req("AZURE_RG_DNS"), azure_dns_zone=envconfig.req("AZURE_DNS_ZONE"), azure_keyvault_name=envconfig.req("AZURE_KEYVAULT_NAME"), acme_contact_email=envconfig.req("ACME_CONTACT_EMAIL"), acme_directory_url=envconfig.req("ACME_DIRECTORY_URL"), cert_request_connection_str=envconfig.req("AzureWebJobsStorage"), cert_request_container=envconfig.opt( "CERT_REQ_CONTAINER", DEFAULT_CERT_REQUESTS_CONTAINER), cert_subject=envconfig.opt("CERT_SUBJECT", DEFAULT_CERT_SUBJECT), cert_key_size=int( envconfig.opt("CERT_KEY_SIZE", str(DEFAULT_CERT_KEY_SIZE))), cert_expiry_threshold_days=int( envconfig.opt( "CERT_EXPIRY_THRESHOLD_DAYS", str(DEFAULT_CERT_EXPIRY_THRESHOLD_DAYS), )), )
def from_env(cls: typing.Type["Options"]) -> "Options": """ Returns a class object of type Options Fields: domain, azure (subscription, resource group, dns zone, traffic manager, principal user id) plus a list of azure geographical locations where the application is running """ return cls( domain=envconfig.req("DOMAIN"), azure_subscription_id=envconfig.req("AZURE_SUBSCRIPTION_ID"), azure_dns_resource_group=envconfig.req("AZURE_RG_DNS"), azure_dns_zone=envconfig.req("AZURE_DNS_ZONE"), azure_trafficmanager_resource_group=envconfig.req( "AZURE_RG_TRAFFICMANAGER" ), azure_trafficmanager_name_prefix=envconfig.req( "TRAFFICMANAGER_NAME_PREFIX" ), azure_appgw_managed_identity={ envconfig.req("AZURE_APPGW_MI"): ManagedIdentityProperties( principal_id=envconfig.req("AZURE_APPGW_MI_PRINCIPAL_ID"), client_id=envconfig.req("AZURE_APPGW_MI_CLIENT_ID"), ) }, locations=[ LocationOptions( azure_resource_group_name=envconfig.req("AZURE_APPGW1_RG"), azure_location=envconfig.req("AZURE_APPGW1_LOCATION"), azure_vnet_name=envconfig.req("AZURE_APPGW1_VNET_NAME"), azure_subnet_name=envconfig.req("AZURE_APPGW1_SUBNET_NAME"), azure_subnet_address_prefix=envconfig.req( "AZURE_APPGW1_SUBNET_ADDR_PREFIX" ), backend_address=envconfig.req("AZURE_APPGW1_BACKEND_ADDRESS"), backend_port=int(envconfig.opt("AZURE_APPGW1_BACKEND_PORT", "80")), backend_protocol=envconfig.opt( "AZURE_APPGW1_BACKEND_PROTOCOL", "Http" ), ), LocationOptions( azure_resource_group_name=envconfig.req("AZURE_APPGW2_RG"), azure_location=envconfig.req("AZURE_APPGW2_LOCATION"), azure_vnet_name=envconfig.req("AZURE_APPGW2_VNET_NAME"), azure_subnet_name=envconfig.req("AZURE_APPGW2_SUBNET_NAME"), azure_subnet_address_prefix=envconfig.req( "AZURE_APPGW2_SUBNET_ADDR_PREFIX" ), backend_address=envconfig.req("AZURE_APPGW2_BACKEND_ADDRESS"), backend_port=int(envconfig.opt("AZURE_APPGW2_BACKEND_PORT", "80")), backend_protocol=envconfig.opt( "AZURE_APPGW2_BACKEND_PROTOCOL", "Http" ), ), ], )
def main(req: func.HttpRequest, msg: func.Out[str]) -> func.HttpResponse: api_baseurl = envconfig.req("API_BASEURL") # Check if JSON is valid try: req_json = req.get_json() except ValueError: return http.invalid_json_response() # Validate data try: req_body = cfg.IngressWhitelistUpdate(**req_json) except pydantic.error_wrappers.ValidationError as e: return http.validation_error_response(e) if len(req_body.ip_whitelist) == 0: return func.HttpResponse( body=http.response_body( success=True, msg="No change", reason="Provided whitelist was an empty list [].", ), status_code=200, headers=http.response_headers(), ) try: # Client for handling state in storage account blob_client = st.new_blob_client() # Validate client id client_id = cfg.Id.from_str(req.route_params.get("client_id")) # Get current configuration from state in Azure state_in_azure = st.StateInAzure(blob_client) state = state_in_azure.get_by_id(client_id) # Append all unique ip addresses to the existing whitelist for ip in req_body.ip_whitelist: if ip not in state.ip_whitelist: state.ip_whitelist.append(ip) # Create IngressUpdate from new state ingress_update = cfg.IngressUpdate(subdomain=state.subdomain, ip_whitelist=state.ip_whitelist) # Update state in Azure and get queue item in return queue_item = state_in_azure.upsert(client_id, ingress_update, datetime.datetime.now()) msg.set(queue_item.json()) response = { "success": True, "msg": "Added to queue.", "poll_url": f"{api_baseurl}/v1/{str(client_id)}/status", "data": json.loads(state.json()), } return func.HttpResponse(json.dumps(response), status_code=202, headers=http.response_headers()) except KeyError: return http.not_found_response( reason=f"Client with id '{client_id}' does not exist") except st.UpdateConflictError as e: return http.update_conflict_response(e)