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

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.

Related

How do I encode the secret for the 'create-passenger-name-record-sample-nodejs' Sabre API example?

I'm trying to run the 'create-passenger-name-record-sample-nodejs' example code. It requires a secret and PCC as shown below:
endpoint: 'https://api.test.sabre.com',
secret: process.env.SWS_API_SECRET || '',
pcc: process.env.SWS_API_PCC || '',
};
According to the documentation, the secret above is a base64 encoding of 'V1:userid:group:domain'.
I have created an account but it is not clear to me where to obtain the pieces necessary to construct the concatenated string above and also where to obtain the PCC.
Is anyone able to provide some guidance?
Thanks.
_Username and password should be provided by your account executive if you have a Sabre subscription. If you do not have a Sabre subscription, you should be able to obtain sample credentials to test in test environments. You can find your test credentials under "Applications" when you click on "My account". The procedure to encode a token, if needed, is located at https://developer.sabre.com/guides/travel-agency/developer-guides/rest-apis-token-credentials.
The solution is to base64 encode the ClientID. Then base64 encode the ClientSecret. Then join those results with a ':' and base64 encode the joined string. This then is used as the value of the 'secret' field in the JSON snippet above.

how to test jwt sample? and what are userid?

My development Environment : eclipse java
I would like test a requestJWTuserToken sample, but I'm getting an error.
Test code:
OAuth.OAuthToken oAuthToken = apiClient.requestJWTUserToken(IntegratorKey, userId, scopes, privateKeyBytes, 3600);
Assert.assertNotSame(null, oAuthToken);
apiClient.setAccessToken(oAuthToken.getAccessToken(), oAuthToken.getExpiresIn());
UserInfo userInfo = apiClient.getUserInfo(oAuthToken.getAccessToken());
Assert.assertNotSame(null, userInfo);
Assert.assertNotNull(userInfo.getAccounts());
Assert.assertTrue(userInfo.getAccounts().size() > 0);
Error message:
com.docusign.esign.client.ApiException: Error while requesting an access token:
class OAuthToken {
accessToken: null
tokenType: null
refreshToken: null
expiresIn: 0
at com.docusign.esign.client.ApiClient.requestJWTUserToken(ApiClient.java:719)
at smartsuite.app.util.DocuSignUtil.settingAuthentication(DocuSignUtil.java:112)
what are userid?
I found a admin sendbox at user > API username
enter image description here
This error is sometimes encountered when there is a problem with the RSA private key that you are using in your JWT request. Start by verifying how you are passing your key's private data into the request, if you are reading from a environment variable for example make sure the key's value is contained on a single line, otherwise it might be getting truncated.
If your key spans multiple lines as an environment variable try doing a regular expression find/replace where you replace all newline \n characters with the string literal "\n" and then pass that through to see if it resolves your issue.

Flutter / Dart : Avoid storing password in plain text

I am using Dart mailer in Flutter and there is a comment that says:
How you use and store passwords is up to you. Beware of storing passwords in plain.
Is there any way to hash the password? How can I avoid storing it in plain text?
It is generally not a good idea to store passwords in plain text anywhere. The way you handle passwords, though, depends on the platform.
Flutter
The flutter_secure_storage package uses Keychain on iOS and KeyStore on Android to store passwords (or tokens).
// Create storage
final storage = FlutterSecureStorage();
// Read secret
String value = await storage.read(key: key);
// Write secret
await storage.write(key: key, value: value);
Note that for Android the min API is 18.
Dart Server
If you are making a server, it is even more important not to store the user passwords in plain text. If the server is compromised, the attacker would have access to all of the passwords, and many users use the same password on multiple accounts.
It would be best to hand the authentication over to Google or Facebook or some other trusted third party by using OAuth2. However, if you are doing your own authorization, you should hash the passwords with a salt and save the hash, not the password itself. This makes it more difficult for an attacker to get the user passwords in case the server is compromised.
A basic implementation (but see comment below) could use the crypto package by the Dart Team.
// import 'package:crypto/crypto.dart';
// import 'dart:convert';
var password = 'password123';
var salt = 'UVocjgjgXg8P7zIsC93kKlRU8sPbTBhsAMFLnLUPDRYFIWAk';
var saltedPassword = salt + password;
var bytes = utf8.encode(saltedPassword);
var hash = sha256.convert(bytes);
Save the salt and the hash. Discard the password. Use a different salt for every user.
To make brute forcing the hashes more difficult, you can also check out the dbcrypt package.
If you want to hash
Use the password_hash package. Their example code is very easy to use:
var generator = new PBKDF2();
var salt = Salt.generateAsBase64String();
var hash = generator.generateKey("mytopsecretpassword", salt, 1000, 32);
Store both the hash and the salt, and you can verify someone else's password attempt by running the generator.generateKey function using their password and the saved salt.
What you actually want
If you're trying to automatically login, you of course need the original password, not a hash. You have a couple options
If the device that will have your app installed is safe, as in it is some company-owned device that has to be logged into by an employee, then have it in plaintext. It doesn't matter. As any company's security policy should be, you must make sure that hard drives are wiped before disposing of electronics (And make sure that no one can stroll in and take the iPad or whatever it is).
If unknown people outside of your organization will be installing your app, you will have to have them login and use their email, or have an API open that will send emails on their behalf (To prevent spamming from your email). The app would sent a POST to that API to send an email. If you had the plaintext password in the application, they could find it on their device, and abuse it.
This response comes late, but here is my approach to storing and using a password for sending emails to recipients using mailer in Flutter. I hope it helps anyone facing this issue.
First I downloaded the crypton package. Then I created a separate dart file where I handle everything related to sending mails, I called it mailer. In this file is where I specify the password, encrypts it using crypton, and use send the email using the decrypted password.
Below is the code of my mailer.dart file:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:mailer/mailer.dart';
import 'package:mailer/smtp_server.dart';
import 'package:intl/intl.dart';
import 'package:crypton/crypton.dart';
class Mailer {
//the variable below we use to encrypt and decrypt the password
RSAKeypair _rsaKeypair = RSAKeypair.fromRandom();
//Below we set the password as a private variable
String _password = 'mySecurePassword';
//We set an encrypted variable that will store the encrypted password;
String _encrypted;
//The function below sets the encrypted variable by assigning it the encrypted value of the password
void setEncrypt () {
_encrypted = _rsaKeypair.publicKey.encrypt(_password);
}
//The function below is responsible for sending the email to the recipients and it is what we call when we want to send an email
emailSender({#required String emailRecipient, #required List<String> paths}) async {
//We call the setEncrypt() function to assign the correct value to the encrypted variable
setEncrypt();
String username = 'email#email.com';
//We asign the decrypted value of encrypted to the password we provide to the smtpServer
String password = _rsaKeypair.privateKey.decrypt(_encrypted);
//The rest of sending an email is the same.
final smtpServer = gmail(username, password);
// Use the SmtpServer class to configure an SMTP server:
// final smtpServer = SmtpServer('smtp.domain.com');
// See the named arguments of SmtpServer for further configuration
// options.
// Create our message.
Message message = Message()
..from = Address(username, 'Your name')
..recipients.add(emailRecipient)
..ccRecipients.addAll(['secondEmail#email.com'])
..subject = 'Date: ${DateFormat('dd/MM/yyyy').format(DateTime.now())}'
..text = 'This is the plain text.\nThis is line 2 of the text part.'
..html = "<h1>Hi:</h1>\n<p>This is some html</p>\n<p>Greetings, mailer.dart</p>";
for (String path in paths) {
message
..attachments.add(
FileAttachment(
File(
path,
),
),
);
}
var connection = PersistentConnection(smtpServer);
// Send the first message
await connection.send(message);
// send the equivalent message
//await connection.send(equivalentMessage);
// close the connection
await connection.close();
}
}
This was my approach to solving the issue of storing passwords as plain text for sending emails using the mailer package or any package with a similar purpose.

Valid Google OAuth2 token unparseable?

I've got a valid OAuth2 token that Google accepts, but GoogleIdTokenVerifier cannot even parse it.
The token is ya29.1.AADtN_XcjzHgauKetBvrbgHImGFg1pjiHRQAKHyTglBDjEZsTPUMQJ5p-xAKtk955_4r6MdnTe3HZ08 (no worries, it's already expired).
It's obtained on Android using
accountManager.blockingGetAuthToken(account, "oauth2:https://www.googleapis.com/auth/userinfo.email", true);
When I call https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=...
I get sane result like
{
"issued_to": "34951113407.apps.googleusercontent.com",
"audience": "34951113407.apps.googleusercontent.com",
"scope": "https://www.googleapis.com/auth/userinfo.email",
"expires_in": 3175,
"email": "me#gmail.com",
"verified_email": true,
"access_type": "offline"
}
So it must be a valid token.
But when I call
new GoogleIdTokenVerifier(new UrlFetchTransport(), JacksonFactory.getDefaultInstance())
.verify(authToken)
It gives me
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('É' (code 201)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
at [Source: java.io.ByteArrayInputStream#69886979; line: 1, column: 2]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1378)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:599)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:520)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._handleUnexpectedValue(UTF8StreamJsonParser.java:2275)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._nextTokenNotInObject(UTF8StreamJsonParser.java:788)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.nextToken(UTF8StreamJsonParser.java:674)
at com.google.api.client.json.jackson2.JacksonParser.nextToken(JacksonParser.java:55)
at com.google.api.client.json.JsonParser.startParsing(JsonParser.java:213)
at com.google.api.client.json.JsonParser.parse(JsonParser.java:372)
at com.google.api.client.json.JsonParser.parse(JsonParser.java:328)
at com.google.api.client.json.JsonParser.parseAndClose(JsonParser.java:158)
at com.google.api.client.json.JsonParser.parseAndClose(JsonParser.java:140)
at com.google.api.client.json.JsonFactory.fromInputStream(JsonFactory.java:206)
at com.google.api.client.json.webtoken.JsonWebSignature$Parser.parse(JsonWebSignature.java:480)
at com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.parse(GoogleIdToken.java:57)
at com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier.verify(GoogleIdTokenVerifier.java:190)
By debugging JsonWebSignature it seems that token payload is just 1.
Android 4.4.2
com.google.http-client:google-http-client-jackson2:1.17.0-rc
com.fasterxml.jackson.core:jackson-core:2.3.0 (also tried included 2.1.3 from transient dependencies of google-http-client-jackson)
Also tried GsonFactory, exception is different, but also clearly cannot be parsed by JsonWebSignature.parse().
What I did wrong? Are there different tokens formats out there?
There are indeed different token formats out there.
The OAuth2 token you have there is the access_token -- it says that your software is authorized with the scope you requested, but it doesn't say anything about which user is actually making the request.
There is another type of token which the GoogleIdTokenVerifier expects to verify: an OpenID Connect id_token. A lot of services use that one, because it means some third party is authenticating that the traffic you're looking at came from that human (more or less!).
There's a little more background here, but the short version is that you should consider using GoogleAuthUtil#getToken(Context, String, String) -- it will return the id_token as a String -- or else consider exactly which scopes you need, and consider requesting the openid scope instead of the current oauth2:https://www.googleapis.com/auth/userinfo.email.
I know this answer is probably too late to help you :)

Grails Spring Security Plugin - Username escaping problem

I'm currently working with Grails and the Spring Security plugin and trying to implement a password expiration workflow. I've configured the plugin as expected:
grails.plugins.springsecurity.failureHandler.exceptionMappings = [
'org.springframework.security.authentication.CredentialsExpiredException': '/login/passwordExpired'
]
and in my passwordExpired action if I call:
def username = session['SPRING_SECURITY_LAST_USERNAME']
then in the username the HTML special characters are going to be escaped like
my_user => my_user
my-user => my-user
Is it possible to turn this escaping off?
Ritesh mentioned here spring_security_last_username that the SPRING_SECURITY_LAST_USERNAME is deprecated, so what else can I use?
For any help, thanks in advance!
The String 'SPRING_SECURITY_LAST_USERNAME' isn't deprecated - the old constant with that value is and has been moved with a new name but the same value. So your code will continue to be valid.
Rather than changing things to not escape, you can un-escape easily:
import org.apache.commons.lang.StringEscapeUtils
...
String username = StringEscapeUtils.unescapeHtml(session['SPRING_SECURITY_LAST_USERNAME'])
You don't need to use a tool. Use Grails HTML codec:
username = session['SPRING_SECURITY_LAST_USERNAME']?.decodeHTML()

Resources