How to create a Parameter Store entry without a value in AWS CDK? - aws-cdk

In AWS CDK, you can create a parameter store entry for storing secrets like passwords.
However you cannot leave the value blank, and you shouldn't put the secret in the CDK git repository, so how do you get the entry created?
I am currently doing something like this:
const paramKey = new cdkSSM.StringParameter(this, 'example-key', {
description: 'Example SSH private key parameter',
parameterName: 'example-key',
stringValue: `???`, /// What goes here?
allowedPattern: '^-----BEGIN RSA PRIVATE KEY-----[^-]*-----END RSA PRIVATE KEY-----$',
});
In this case, I can't leave the stringValue blank or I get an error, as Parameter Store does not allow blank values. The only value it will accept is an RSA private key, due to the allowedPattern requirement (which is a safety measure to stop someone from accidentally putting in an invalid value through the AWS CLI). But I don't want to put my private key in as it should not be part of the CDK git repository. I don't want to use a dummy key as then someone might think the correct key has been entered already.
How can I deploy a blank Parameter Store value while having the allowedPattern present?
The only workaround I have come up with is to hack the value to allow another token as well, like this:
const paramKey = new cdkSSM.StringParameter(this, 'example-key', {
description: 'Example SSH private key parameter',
parameterName: 'example-key',
stringValue: `TODO`,
allowedPattern: '^TODO$|^-----BEGIN RSA PRIVATE KEY-----[^-]*-----END RSA PRIVATE KEY-----$',
});
This means the Parameter Store entry will accept either an RSA key or the value TODO. But this seems very hacky so I am wondering whether there is a proper solution for this?

Related

AWS CDK DocDB::DBCluster fails with 'not a valid password'

I am trying to use AWS CKD (JAVA) to create a DocumentDB instance.
This works with a "simple" plaintext password, but fails when I try to use a DatabaseSecret and a password stored in Secrets Manager.
The error I get is this:
1:44:42 PM | CREATE_FAILED | AWS::DocDB::DBCluster | ApiDocDb15EB2C21
The parameter MasterUserPassword is not a valid password. Only printable ASCII characters besides '/', '#', '"', ' ' may
be used. (Service: AmazonRDS; Status Code: 400; Error Code: InvalidParameterValue; Request ID: c786d247-8ff2-4f30-9a8a-5
065fc89d3d1; Proxy: null)
which is clear enough, but it continues to happen, even if I set the password to something such as simplepassword - so I am now somewhat confused as to what am I supposed to fix now.
Here is the code, mostly adapted from the DocDB documentation:
String id = String.format(DOCDB_PASSWORD_ID);
return DatabaseSecret.Builder.create(scope, id)
.secretName(store.getSsmSecretName())
.encryptionKey(passwordKey)
.username(store.getAdminUser())
.build();
where the ssmSecretName is the name of the secret in SecretManager:
└─( aws secretsmanager get-secret-value --secret-id api-db-admin-pwd
ARN: arn:aws:secretsmanager:us-west-2:<ACCT>:secret:api-db-admin-pwd-HHxpFf
Name: api-db-admin-pwd
SecretString: '{"api-db-admin-pwd":"simplepassword"}'
This is the code used to build the DbCluster:
DatabaseCluster dbCluster = DatabaseCluster.Builder.create(scope, id)
.dbClusterName(properties.getDbName())
.masterUser(Login.builder()
.username(properties.getAdminUser())
.kmsKey(passwordKey)
.password(masterPassword.getSecretValue())
.build())
.vpc(vpc)
.vpcSubnets(ISOLATED_SUBNETS)
.securityGroup(dbSecurityGroup)
.instanceType(InstanceType.of(InstanceClass.MEMORY5, InstanceSize.LARGE))
.instances(properties.getReplicas())
.storageEncrypted(true)
.build();
The question I have is: should I use a DatabaseSecret? or just retrieve the password from SM and be done with it?
A sub-question then: what is one supposed to use the DatabaseSecret for then?
(NOTE -- this is the same class, almost, as in the rds package; but here I am using the docdb package)
Thanks for any suggestion!
Turns out that the DatabaseSecret creates a key/value pair as the secret:
{
"username": <value of username()>,
"password": <generated>
}
However, the call to Login.password() completely ingnores this, and treats the whole JSON body as the password (so the " double quotes trip it).
The trick is to use DatabaseSecret.secretValueFromJson("password") in the call to Login.password() and it works just fine.
This is (incidentally) inconsistent with the behavior of rds.DatabaseCluster and the rds.Credentials class behavior (who take a JSON SecretValue and parse it correctly for the "password" field).
Leaving it here in case others stumble on this, as there really is NO information out there.

get-azurekeyvaultkey does not return "public key"

I am just testing out Azure Key Vault with key/pairs and am attempting to retrieve the public key.
I first created a Key Vault (name = "VaultTest") using Azure portal.
I then created a Key (Name = "TestKey1") again using Azure portal.
I see the key in the portal and when I click on it I see the following information:
Properties:
Key Type: RSA
RSA Key Size 2048
Created: "date time"
Updated: "date time"
Key Identifier: //vault path/keys/TestKey1/Key identifier
Settings:
Set activation date: "unchecked"
Set expiration date: "unchecked"
Enabled: True
Tags "none"
Permitted operations:
Encrypt: true
Decrypt: true
Sign: true
Verify: true
Wrap key: true
Unwrap key: true
Notice that there is no public key information displayed so I switched over to Azure Cloud Shell and executed the following command:
Get-AzureKeyVaultKey -vaultname 'VaultTest' -name 'TestKey1'
It returns VaultName, Name, Version, Id, Enabled, Expires, Not Before, Created, Updated, Purge Disabled and Tags, but no Key.
All the examples I read online (albeit somewhat old) show fields Attributes and Key being returned but those are not returning for me.
I read somewhere that if you call the URI it will return the public key info, so I copy/pasted the URI into a browser but this returns to me:
{"error":{"code":"Unauthorized","message":"Request is missing a Bearer or PoP token."}}
Am I doing something brain dead or has the function get-azurekeyvaultkey changed? If it has changed how does one get the public key information for a specific key stored in Key Vault?
I can reproduce your issue with Get-AzureKeyVaultKey -vaultname 'VaultTest' -name 'TestKey1'.
But actually it returns the Attributes and Key that you want, just pass the | ConvertTo-Json like below.
Get-AzureKeyVaultKey -vaultname 'VaultTest' -name 'TestKey1' | ConvertTo-Json

How can I do public key pinning in Flutter?

I want to the pin the public key of my server so that any request made to the server has to have that public key (this is to prevent proxies like Charles sniffing the data).
I had done something similar in Android with Volley.
How can I do the same with Flutter?
Create your client with a SecurityContext with no trusted roots to force the bad certificate callback, even for a good certificate.
SecurityContext(withTrustedRoots: false);
In the bad certificate callback, parse the DER encoded certificate using the asn1lib package. For example:
ASN1Parser p = ASN1Parser(der);
ASN1Sequence signedCert = p.nextObject() as ASN1Sequence;
ASN1Sequence cert = signedCert.elements[0] as ASN1Sequence;
ASN1Sequence pubKeyElement = cert.elements[6] as ASN1Sequence;
ASN1BitString pubKeyBits = pubKeyElement.elements[1] as ASN1BitString;
List<int> encodedPubKey = pubKeyBits.stringValue;
// could stop here and compare the encoded key parts, or...
// parse them into their modulus/exponent parts, and test those
// (assumes RSA public key)
ASN1Parser rsaParser = ASN1Parser(encodedPubKey);
ASN1Sequence keySeq = rsaParser.nextObject() as ASN1Sequence;
ASN1Integer modulus = keySeq.elements[0] as ASN1Integer;
ASN1Integer exponent = keySeq.elements[1] as ASN1Integer;
print(modulus.valueAsBigInteger);
print(exponent);
Key rotation reduces risk. When an attacker obtains an old server hard drive or backup file and gets an old server private key from it, they cannot impersonate the current server if the key has been rotated. Therefore always generate a new key when updating certificates. Configure the client to trust the old key and the new key. Wait for your users to update to the new version of the client. Then deploy the new key to your servers. Then you can remove the old key from the client.
Server key pinning is only needed if you're not rotating keys. That's bad security practice.
You should do certificate pinning with rotation. I have added example code in How to do SSL pinning via self generated signed certificates in flutter?

How to generate a Tink key from a user-provided password

I want to store a keyset, and would like the file to be encrypted with key produced from a user-provided "master password". And of course, at a later point I'd like to, given the same master password, be able to load that keyset by decrypting the file.
It seems that I need an Aead, which I can generate from a KeysetHandle with AeadFactory.getPrimitive(keysetHandle). But how can I produce a KeysetHandle from a "master password"?
(And for the context of this question, getting that key from a Key Management Systems, instead of producing it "out of thin air" from a master password, isn't an option.)
An Aead can be created as follows (here done from Scala):
val password: String = "..."
val aead = {
val messageDigest = MessageDigest.getInstance("SHA-256")
messageDigest.update(password.getBytes(CharsetNames.Utf8))
val key256Bit = messageDigest.digest()
val key128Bit = key256Bit.take(16)
new AesGcmJce(key128Bit)
}
A few comments:
I'd prefer the key to be based on a 32-bit digest, but the cipher picked by Tink in this case throws an exception when provided with a 32-bit key, hence the shortening to a 16-bit key.
It seems shortening the key this way is ok from a hash weakness perspective.

Desire2Learn issue with calling API's

Am trying to use the valance api to call few methods. Am authenticating using https://apitesttool.desire2learnvalence.com from where am getting UserId & UserKey. Now am confused what should i pass in the x_a - x_d parameters for getting the organization info.
What ever i pass i get a 403 forbidden & incorrect token exception.
Some body please help. Am passing folling in the parameters.
x_a : Application ID
x_b : User ID( I got this from https://apitesttool.desire2learnvalence.com)
x_c : private String calculateParameterExpectation( String key, String httpMethod, String apiPath, long timestamp)
{
String unsignedResult = String.format("%s&%s&%s", httpMethod, apiPath, timestamp);
System.out.println(unsignedResult);
String signedResult = D2LSigner.getBase64HashString(key, unsignedResult);
return signedResult;
} Where key is the App Key
x_d : private String calculateParameterExpectation(
String key, String httpMethod, String apiPath, long timestamp) {
String unsignedResult = String.format("%s&%s&%s", httpMethod, apiPath, timestamp);
System.out.println(unsignedResult);
String signedResult = D2LSigner.getBase64HashString(key, unsignedResult);
return signedResult;
} Where key is the User Signature that i got from https://apitesttool.desire2learnvalence.com
Am not sure what is done wrong.
Please note that each back-end service generates a unique UserID/Key pair to go with each user and each application ID, upon request by a call from that application ID.
This explicitly means that User ID/Key pairs are not transferrable from one application to another. Nor are they transferrable from one back-end service to another -- every API-using application should request its own UserID/Key pair for making calls on behalf of each distinct user. Even if you used your App ID/Key when using the api-test tool, unless you pointed the tool at the same back-end service you're actually making the API calls against, you won't get back a UserID/Key pair you can use for later making the API calls (against another service).
Please also note that the signing mechanism requires that you use the upper-case version of the http-method string (thus GET, not get), and it requires that you use the lower-case version of the api path string (thus /d2l/auth/api/token, not /D2L/AUTH/API/TOKEN). If you're pointing the api-test tool at the same LMS you're wanting to make API calls against, and you're using the same App ID/Key pair with the api-test tool as you're using in your production code, then I would seek to make sure that you're formatting your base string exactly right for signing.
I would also encourage you to make fuller use D2L's own client SDK libraries for doing the app/user context management and signing, rather than just using the raw signing call from within the library.

Resources