I have followed Create an Azure Key Vault-backed secret scope to integrate Databricks with Key Vault and all works ok. Unfortunately this requires manual intervention, which breaks our 'full automated infrastructure' approach. Is there any way to automate this step?
UPDATE: You create a Databricks-backed secret scope using the Databricks CLI (version 0.7.1 and above). Alternatively, you can use the Secrets API.
It does not appear that Azure Key Vault backed secret scope creation has a publicly available API call, unlike the Databricks backed secret scope creation. This is backed by the 'Note' on the secret scopes doc page:
Creating an Azure Key Vault-backed secret scope is supported only in the Azure Databricks UI. You cannot create a scope using the Secrets CLI or API.
A request for the feature you are asking for was made last year, but no ETA was given.
I took a look at the request made by the UI page. While the form data is simple enough, the headers and security measures make programmatic access impractical. If you are dead-set on automating this part, you could use one of those tools which automates the cursor around the screen and clicks things for you.
Now it is possible, but you can't use a service principal token. It must be a user token which hinder automation.
Refer to Microsoft Docs:
https://learn.microsoft.com/en-us/azure/databricks/security/secrets/secret-scopes#create-an-azure-key-vault-backed-secret-scope-using-the-databricks-cli
You can use Databricks Terraform provider to create secret scope baked by the Azure KeyVault. But because of Azure limitations it should be done by using user’s AAD token (usually using azure cli). Here is the working snippet for creation of the secret scope from existing KeyVault:
terraform {
required_providers {
databricks = {
source = "databrickslabs/databricks"
version = "0.2.9"
}
}
}
provider "azurerm" {
version = "2.33.0"
features {}
}
data "azurerm_databricks_workspace" "example" {
name = var.workspace_name
resource_group_name = var.resource_group
}
provider "databricks" {
azure_workspace_resource_id = data.azurerm_databricks_workspace.example.id
}
data "azurerm_key_vault" "example" {
name = var.keyvault_name
resource_group_name = var.resource_group
}
resource "databricks_secret_scope" "example" {
name = data.azurerm_key_vault.example.name
keyvault_metadata {
resource_id = data.azurerm_key_vault.example.id
dns_name = data.azurerm_key_vault.example.vault_uri
}
}
variable resource_group {
type = string
description = "Resource group to deploy"
}
variable workspace_name {
type = string
description = "The name of DB Workspace"
}
variable keyvault_name {
type = string
description = "The name of DB Workspace"
}
Related
I'm using terraform to write azure policy as code
I found two problems
1 I can't seem to use log analytics workspace that is on a different subscription, within same subscription, it's fine
2 For policies that needs managed identity, I can't seem to assign correct rights to it.
resource "azurerm_policy_assignment" "Enable_Azure_Monitor_for_VMs" {
name = "Enable Azure Monitor for VMs"
scope = data.azurerm_subscription.current.id
policy_definition_id = "/providers/Microsoft.Authorization/policySetDefinitions/55f3eceb-5573-4f18-9695-226972c6d74a"
description = "Enable Azure Monitor for the virtual machines (VMs) in the specified scope (management group, subscription or resource group). Takes Log Analytics workspace as parameter."
display_name = "Enable Azure Monitor for VMs"
location = var.location
metadata = jsonencode(
{
"category" : "General"
})
parameters = jsonencode({
"logAnalytics_1" : {
"value" : var.log_analytics_workspace_ID
}
})
identity {
type = "SystemAssigned"
}
}
resource "azurerm_role_assignment" "vm_policy_msi_assignment" {
scope = azurerm_policy_assignment.Enable_Azure_Monitor_for_VMs.scope
role_definition_name = "Contributor"
principal_id = azurerm_policy_assignment.Enable_Azure_Monitor_for_VMs.identity[0].principal_id
}
for var.log_analytics_workspace_ID, if i use the workspace id that is in the same subscription as the policy, it would work fine. but If I use a workspace ID from a different subscription, after deployment, the workspace field will be blank.
also for
resource "azurerm_role_assignment" "vm_policy_msi_assignment"
, I have already given myself user access management role, but after deployment, "This identity currently has the following permissions:" is still blank?
I got an answer to my own question:)
1 this is not something designed well in Azure, I recon.
MS states "a Managed Identity (MSI) is created for each policy assignment that contains DeployIfNotExists effects in the definitions. The required permission for the target assignment scope is managed automatically. However, if the remediation tasks need to interact with resources outside of the assignment scope, you will need to manually configure the required permissions."
which means, the system generated managed identity which needs access in log analytics workspace in another subscription need to be manually with log analytics workspace contributor rights
Also since you can't user user generated managed ID, you can't pre-populate this.
so if you want to to achieve in terraform, it seems you have to run policy assignment twice, the first time is just to get ID, then manual ( or via script) to assign permission, then run policy assignment again to point to the resource..
2 The ID was actually given the contributor rights, you just have to go into sub RBAC to see it.
I have a running cloud run service user-service. For test purposes I passed client secrets via environment variables as plain text. Now since everything is working fine I'd like to use a secret instead.
In the "Variables" tab of the "Edit Revision" option I can declare environment variables but I have no idea how to pass in a secret? Do I just need to pass the secret name like ${my-secret-id} in the value field of the variable? There is not documentation on how to use secrets in this tab only a hint at the top:
Store and consume secrets using Secret Manager
Which is not very helpful in this case.
You can now read secrets from Secret Manager as environment variables in Cloud Run. This means you can audit your secrets, set permissions per secret, version secrets, etc, and your code doesn't have to change.
You can point to the secrets through the Cloud Console GUI (console.cloud.google.com) or make the configuration when you deploy your Cloud Run service from the command-line:
gcloud beta run deploy SERVICE --image IMAGE_URL --update-secrets=ENV_VAR_NAME=SECRET_NAME:VERSION
Six-minute video overview: https://youtu.be/JIE89dneaGo
Detailed docs: https://cloud.google.com/run/docs/configuring/secrets
UPDATE 2021: There is now a Cloud Run preview for loading secrets to an environment variable or a volume. https://cloud.google.com/run/docs/configuring/secrets
The question is now answered however I have been experiencing a similar problem using Cloud Run with Java & Quarkus and a native image created using GraalVM.
While Cloud Run is a really interesting technology at the time of writing it lacks the ability to load secrets through the Cloud Run configuration. This has certainly added complexity in my app when doing local development.
Additionally Google's documentation is really quite poor. The quick-start lacks a clear Java example for getting a secret[1] without it being set in the same method - I'd expect this to have been the most common use case!
The javadoc itself seems to be largely autogenerated with protobuf language everywhere. There are various similarly named methods like getSecret, getSecretVersion and accessSecretVersion
I'd really like to see some improvment from Google around this. I don't think it is asking too much for dedicated teams to make libraries for common languages with proper documentation.
Here is a snippet that I'm using to load this information. It requires the GCP Secret library and also the GCP Cloud Core library for loading the project ID.
public String getSecret(final String secretName) {
LOGGER.info("Going to load secret {}", secretName);
// SecretManagerServiceClient should be closed after request
try (SecretManagerServiceClient client = buildClient()) {
// Latest is an alias to the latest version of a secret
final SecretVersionName name = SecretVersionName.of(getProjectId(), secretName, "latest");
return client.accessSecretVersion(name).getPayload().getData().toStringUtf8();
}
}
private String getProjectId() {
if (projectId == null) {
projectId = ServiceOptions.getDefaultProjectId();
}
return projectId;
}
private SecretManagerServiceClient buildClient() {
try {
return SecretManagerServiceClient.create();
} catch(final IOException e) {
throw new RuntimeException(e);
}
}
[1] - https://cloud.google.com/secret-manager/docs/reference/libraries
Google have documentation for the Secret manager client libraries that you can use in your api.
This should help you do what you want
https://cloud.google.com/secret-manager/docs/reference/libraries
Since you haven't specified a language I have a nodejs example of how to access the latest version of your secret using your project id and secret name. The reason I add this is because the documentation is not clear on the string you need to provide as the name.
const [version] = await this.secretClient.accessSecretVersion({
name: `projects/${process.env.project_id}/secrets/${secretName}/versions/latest`,
});
return version.payload.data.toString()
Be sure to allow secret manager access in your IAM settings for the service account that your api uses within GCP.
I kinda found a way to use secrets as environment variables.
The following doc (https://cloud.google.com/sdk/gcloud/reference/run/deploy) states:
Specify secrets to mount or provide as environment variables. Keys
starting with a forward slash '/' are mount paths. All other keys
correspond to environment variables. The values associated with each
of these should be in the form SECRET_NAME:KEY_IN_SECRET; you may omit
the key within the secret to specify a mount of all keys within the
secret. For example:
'--update-secrets=/my/path=mysecret,ENV=othersecret:key.json' will
create a volume with secret 'mysecret' and mount that volume at
'/my/path'. Because no secret key was specified, all keys in
'mysecret' will be included. An environment variable named ENV will
also be created whose value is the value of 'key.json' in
'othersecret'. At most one of these may be specified
Here is a snippet of Java code to get all secrets of your Cloud Run project. It requires the com.google.cloud/google-cloud-secretmanager artifact.
Map<String, String> secrets = new HashMap<>();
String projectId;
String url = "http://metadata.google.internal/computeMetadata/v1/project/project-id";
HttpURLConnection conn = (HttpURLConnection)(new URL(url).openConnection());
conn.setRequestProperty("Metadata-Flavor", "Google");
try {
InputStream in = conn.getInputStream();
projectId = new String(in.readAllBytes(), StandardCharsets.UTF_8);
} finally {
conn.disconnect();
}
Set<String> names = new HashSet<>();
try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) {
ProjectName projectName = ProjectName.of(projectId);
ListSecretsPagedResponse pagedResponse = client.listSecrets(projectName);
pagedResponse
.iterateAll()
.forEach(secret -> { names.add(secret.getName()); });
for (String secretName : names) {
String name = secretName.substring(secretName.lastIndexOf("/") + 1);
SecretVersionName nameParam = SecretVersionName.of(projectId, name, "latest");
String secretValue = client.accessSecretVersion(nameParam).getPayload().getData().toStringUtf8();
secrets.put(secretName, secretValue);
}
}
Cloud Run support for referencing Secret Manager Secrets is now at general availability (GA).
https://cloud.google.com/run/docs/release-notes#November_09_2021
What is the correct and easy way of changing ASC Default PARAMETERS in Azure policy using TF?
As an example Set Monitor SQL Encryption to AuditIfNotExists or any other available value than Disabled.
'ASC Default' is initiative, or so called azurerm_policy_set_definition in TF terms. You just need to assign it using 'azurerm_policy_assignment'.
These links might help:
https://github.com/Azure/azure-policy/blob/master/built-in-policies/policySetDefinitions/Security%20Center/AzureSecurityCenter.json
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/policy_assignment
Example for different initiative:
resource "azurerm_policy_assignment" "audit_k8s_security_restricted_standarts" {
name = "42b8ef37-b724-4e24-bbc8-7a7708edfe00"
scope = local.azure_policy_scope
policy_definition_id = "/providers/Microsoft.Authorization/policySetDefinitions/42b8ef37-b724-4e24-bbc8-7a7708edfe00"
description = "This initiative includes the policies for the Kubernetes cluster pod security restricted standards."
display_name = "Kubernetes cluster pod security restricted standards for Linux-based workloads"
identity { type = "SystemAssigned" }
location = var.primary_location
parameters = <<PARAMETERS
{
"effect": {
"value": "audit"
}
}
PARAMETERS
}
What you need is to place proper policy_definition_id and pass valid parameters. And start small, with one simple param, there are bugs in TF.
when generating Service Principal in Azure manually, as a result of the operation I'm provided a password.
It's not the case however if I create service principal with Terraform, the password is not among the outputs of this module:
+ azuread_service_principal.k8s_principal
id: <computed>
application_id: "${azuread_application.app.application_id}"
display_name: <computed>
Is there anything I missed? Why does the Terraform behavior differs in the output compared to CLI?
password is required INPUT to the azuread_service_principal_password block. As such, you can generate a random password and export it yourself. Complete Terraform code is something like this:
resource "azuread_application" "app" {
name = "${local.application_name}"
}
# Create Service Principal
resource "azuread_service_principal" "app" {
application_id = "${azuread_application.app.application_id}"
}
resource "random_string" "password" {
length = 32
special = true
}
# Create Service Principal password
resource "azuread_service_principal_password" "app" {
end_date = "2299-12-30T23:00:00Z" # Forever
service_principal_id = "${azuread_service_principal.app.id}"
value = "${random_string.password.result}"
}
output "sp_password" {
value = "${azuread_service_principal_password.app.value}"
sensitive = true
}
to who using newer version of Terraform, you don't need to preset the password, following code is working fine:
resource "azuread_service_principal_password" "auth_pwd" {
service_principal_id = azuread_service_principal.auth.id
}
output "auth_client_secret" {
value = azuread_service_principal_password.auth_pwd.value
description = "output password"
sensitive = true
}
then you can run the following cli to retrieve the password:
terraform output -raw auth_client_secret
tested on terraform 1.0.10, hashicorp/azuread provider 2.11
In the terraform document, the azuread_service_principal block only defines the Argument application_id and Attributes id, display_name, So you only could see these resources. Also, the azuread_service_principal_password block allows you to export the Key ID for the Service Principal Password. You still could not see the real password.
In the Azure CLI az ad sp create-for-rbac has an optional parameter --Password. So you could see the password output.
What I got so far:
In a project I have an authorization server (Identity Server 4), some (let's say two) protected APIs (Api Resource) and some trusted clients (automated, no user interaction) which should access the Identity Server via the backchannel (right?). Imagine the client is a Amazon Fire TV box kind thingy.
According to what I have read so far over the last weeks a suitable flow for this scenario is the OpenID Connect Authorization Code Flow.
clients are trusted (and can maintain a secret)
Authorization Code flow supports refresh tokens (which I want to use)
the client is actually not the resource owner but requires access to the full api resource
What I have in my (theoretical) structure:
I have two API Resources (one resource for each API version)
api.v1
api.v2
I also have two series of my API clients
client.v1 supports only api v1 & should only have access to api.v1 resource
client.v2 supports api v1 & v2 and therefore should have access to both api resources
Identity Server 4 StartUp.cs configuration (so far)
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources
(
new List<ApiResource>
{
new ApiResource("api.v1", "API v1"),
new ApiResource("api.v2", "API v2")
}
)
.AddInMemoryClients
(
new List<Client>
{
new Client
{
ClientId = "client.v1",
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowAccessTokensViaBrowser = false,
ClientSecrets = { new Secret("secret1".Sha256()) },
AllowedScopes = { "api.v1" }
},
new Client
{
ClientId = "client.v2",
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowAccessTokensViaBrowser = false,
ClientSecrets = { new Secret("secret2".Sha256()) },
AllowedScopes = { "api.v1", "api.v2" }
}
}
);
}
The theory what I am struggling with is the authorization code part.
I want to have each client instance (again imagine it as a small box) a different authorization code allowing one instance access but deny fo another one.
Is the authorization code intended to be used for that?
And one important thing I haven't understood in all the time: CodeAndClientCredentials defines two grant types. Does this mean connecting with that requires both (code AND client credentials) or is it an one of them definition (code OR client credentials).
The Identity Server 4 code I am struggling with is:
In the code defining the client I can only find AuthorizationCodeLifetime but no field to set the authorization code itself.
It seems I can define a list of client secrets.
ClientSecrets = { new Secret("secret1".Sha256()) },
Does this mean one client Id can have multiple secrets used? Are different client secrets better suitable for my "allow one deny the other" problem?
Edit
Ok, I have re-read that and now I got it (at least a bit more): the authorization code is not defined sent by the client but the client receives it.
The authorization code flow returns an authorization code (like it says on the tin) that can then be exchanged for an identity token and/or access token. This requires client authentication using a client id and secret to retrieve the tokens from the back end
from this blog here
But how would I have to configure my Identity Server to allow one instance and deny another.
By using different client secrets? Using extension grants?