Package: aenv
AEnvA Python-based CLI tool to simplify the process of fetching and injecting environment variables from AWS Parameter Store.Table of ContentsDescriptionInstallationUsageSetupPermissionsConceptAccess-parameter-store-entriesAuthenticationTodosAcknowledgmentsLicenseDescriptionThis CLI tool (aenv) allows you to fetch environment variables from AWS Parameter Store, injecting them into your local environment for use in your applications. The tool also supports authentication with MFA, including Yubikeys.InstallationPrerequisitesInstall python3 and pipInstall AWS CLIBoto3 (Will be installed with the aenv package)Windows:setup Boto3 credentialsMain Package (aenv)pip install aenvUsageaenv --help
# or
aenv -hAll current options:aenv [-s <service/application>] [-i] [-n] [-e <env>] [-t <2fa key>] [-T] [-Y] [-u <aws username>] [-a <account number>] [-p <aws profile>] [-r <region>] <command>OptionExplanationSampleComment-hShows helpaenv -h-iStarts aenv in interactive modeaenv -iGives you a command line that you can interact with-s <service/application>For which service should the environment variables be loaded?aenv -s CustomerService-SSets a default service for aenv and writes it to a config fileaenv -S CustomerServicefrom now on "CustomerService" is the default service which means "-s CustomerService" is redundant-nDo not query the parameter store at allaenv -nCan be used to auth the current session with MFA-e <env>For which environment should the environment variables be loaded? For example Dev, Test or Prod (permission required)aenv -e Prod-t <2fa key>Takes the 2FA key from your aws accountaenv -t 987123-TLets you type in the 2FA key from your aws account during runtimeaenv -TWhen you run your command aenv will ask for the token-YUses Yubikey for MFA authaenv -YDuring runtime aenv will use ykman to fetch the MFA-Key from your yubikey-r <region>Overwrites temporary the awscli default regionaenv -r eu-central-1aenv will use the given region for example Frankfurt-vVerbose mode (more output)aenv -v-u <aws username>Sets a specific username combined with -a gives you a faster runtime (otherwise this data needs to be retrieved via aws)aenv -u user@example.de-a <account number>Sets a specific account number combined with -u gives you a faster runtime (otherwise this data needs to be retrieved via aws)aenv -a 999999999999-p <aws profile>If multiple aws profiles are available you can choose the profile otherwise aenv will use the default profileaenv -p testUser1-c <aws profile>Container mode(enable this to make aenv work in ecs and codebuild)aenv -cpermissions<command>Is the command to execute with environment variables injected.aenv codeWill run VS Code with access to given environment variablesSetupLets start with setting up a simple example service. Let's call itUserService.TheUserServiceneeds adatabase hostname, adatabase usernameand adatabase password.Let's assume, we have two environments, "Dev" and "Test" and we want to inject the correctUserService,database hostname,... for the related environment.Step 1 is to create the AWS parameter store entries.
Let's create two entries:/Dev/UserService/DB/hostname
# and 
/Test/UserService/DB/hostnameBoth are SecureString, that hold the values:db.dev.example.com for /Dev/UserService/DB/hostname
and
db.test.example.com for /Test/UserService/DB/hostnameStep 2 is to fetch those values and have them available as environment variables.For theUserServicevariables on Dev run:aenv -e Dev -s UserServiceThis command fetches all entries from the parameter store with the path /Dev/UserService/* and makes them available as environment variables.If you just want to echo out the DB hostname we created (/Dev/UserService/DB/hostname) you can run:aenv -e Dev -s UserService echo '$SECRET_USERSERVICE_DB_HOSTNAME'You can also run your service with aenv to have the correct DB hostname available in the service as an environment variable.
The call for a Python or JVM service would be:aenv -e Dev -s UserService java -jar service.jar
# or
aenv -e Dev -s UserService python service2.pyBoth services now have access to the environment variable "SECRET_USERSERVICE_DB_HOSTNAME" containing the value that we defined for "/Dev/UserService/DB/hostname".PermissionsPermissions (User)Here is the minimal suggested set of IAM permissions to use aenv for all services that can be found for our Dev environment:(Do not forget to adapt to account ID(123456789098) to your own ){
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:GetParametersByPath",
                "ssm:GetParameters",
                "ssm:ListTagsForResource",
                "ssm:GetParameter"
            ],
            "Resource": [
                "arn:aws:ssm:*:123456789098:parameter/Dev/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "ssm:DescribeParameters",
            "Resource": "*"
        }
    ]
}These permissions allow to fetch all entries for any service in the Dev environment.For example, fetching all environment variables for a service called UserService:aenv -e Dev -s UserServiceTo further limit access to only allow loading environment variables for a specific service like "UserService" we need to adapt the "Resource":"Resource": [
                "arn:aws:ssm:*:123456789098:parameter/Dev/UserService/*"
            ]Permissions (AWS)This is a work in progress!Currently, the yubikey needs to be added as avirtual mfaand needs to be thefirstdevice in our Multi-factor authentication devices.
Feel free to also add your Yubikey as a hardware mfaafterwards.(The AWS web console works flawless with multiple mfa's)pip install --user yubikey-managerOfficial documentationPermissions (IAM policies / Instance roles)PermissionUsed in the codeDocumentationComment"ec2:DescribeTags"clientEC2.describe_tags()https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeTags.html"sts:GetCallerIdentity"clientSTS.get_caller_identity()https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.htmlOptional(No permissions are required to perform this operation.)"sts:GetSessionToken"clientSTS.get_session_token()https://docs.aws.amazon.com/STS/latest/APIReference/API_GetSessionToken.html"ssm:GetParametersByPath"clientSSMMFA.get_parameters_by_path()https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-access.html"iam:ListMFADevices"boto3.client('iam').list_mfa_devices()https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_iam_mfa-selfmanage.htmlOptional! (At the moment not in use but as soon aws API supports hardware tokens this can be enabled to let aenv support hardware MFA's)tldr Minimal permissions:“ec2:DescribeTags”“sts:GetSessionToken”“ssm:GetParametersByPath”Advanced permission 1 (Enforce MFA authentication for accessing Prod parameters)To enforce MFA authentication for all Prod parameters you can make use of the condition "MultiFactorAuthPresent" in your IAM permission.{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ssm:PutParameter",
                "ssm:DeleteParameter",
                "ssm:GetParameterHistory",
                "ssm:GetParametersByPath",
                "ssm:GetParameters",
                "ssm:ListTagsForResource",
                "ssm:GetParameter",
                "ssm:DeleteParameters"
            ],
            "Resource": [
                "arn:aws:ssm:*:123456789098:parameter/Prod/*"
            ],
            "Condition": {
                "Bool": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "ssm:DescribeParameters",
            "Resource": "*"
        }
    ]
}Advanced permission 2 (Enforce MFA authentication for AWS feature / function)Add the condition "MultiFactorAuthPresent" to your IAM permission:"Condition": {"Bool": {"aws:MultiFactorAuthPresent": "true"}}Sample for sts:AssumeRole:{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Principal": {"AWS": "ACCOUNT-B-ID"},
    "Action": "sts:AssumeRole",
    "Condition": {"Bool": {"aws:MultiFactorAuthPresent": "true"}}
  }
}Now you need MFA authentication to run assume role commands.
Sample call for this would be:aenv -v -n -Y aws sts assume-role --role-arn "arn:aws:iam::123456789012:role/example-role" --role-session-name AWSCLI-Session

# -v enables verbose mode
# -n puts aenv in only authentication mode
# -Y authenticates the session with your YubiKey, alternatively you could use -t or -TConceptAEnv uses the parameter store path to define the environment and service name, following this schema:/<Environment>/<Service-Name>/

# Which could look like:
/Prod/CustomerManagement/DB/USER
# Or
/Prod/CustomerManagement/DB/PASSWORDHaving those two in place would enable ourCustomerManagementservice running in our Prod environment to access, the environment variables:
SECRET_CUSTOMERMANAGEMENT_DB_USER
and
SECRET_CUSTOMERMANAGEMENT_DB_PASSWORDWith both parameters in place, yourCustomerManagementapplication/service, launched with aenv, could now access the database with the provided username and password.aenv -e Prod -s CustomerManagement java -jar service.jarFormat for these environment variables:Every environment variable that is loaded with aenv starts with "SECRET_".Then the service-name and path, separated by underliners. (of course in upper case)For example:/Prod/CustomerManagement/DB/USERwould be accessible with:SECRET_CUSTOMERMANAGEMENT_DB_USERor/Prod/CustomerManagement/DB/PASSWORD/USER1would be accessible with:SECRET_CUSTOMERMANAGEMENT_DB_PASSWORD_USER1More about environment variables:Guide to Unix/Environment VariablesAccess-parameter-store-entriesTesting single variablesLinux/Macaenv -e Dev -s UserService echo '$SECRET_USERSERVICE_UI_URL'orWindowsaenv -e Dev -s UserService echo %SECRET_USERSERVICE_UI_URL%How to access the environment variables in Kotlin and PythonHow to access the environment variablesTo access those environment variables you have to run your application/service with aenv.aenv -e Dev -s UserService java -jar service.jar
//or
aenv -e Dev -s UserService python service2.pyNow these two services have access to all Dev environment variables for the UserService.
Here are easy examples for Python and Kotlin:Python:import os
os.getenv('SECRET_USERSERVICE_HOSTNAME')

# For example conneting to a host depending on the environment:
....

hostname = os.getenv('SECRET_USERSERVICE_HOSTNAME')
....Kotlin:val envVar : String? = System.getenv("SECRET_USERSERVICE_HOSTNAME")Bonus:Running a local application with access to environment variables of a given serviceLinux/Mac running IntelliJ with Test environment variables for theUserServiceaenv -e Test -s UserService "/Applications/IntelliJ\ IDEA\ CE.app/Contents/MacOS/idea"This can come in handy if you want to debug something that only seems to occure in the Test environment.AuthenticationAWS Server:Easy!Done by boto3. Boto3 automatically uses the in the instance role defined permissions.(Details "Permissions" section)Developer:boto3 uses the aws CLI's authentication so make sure you set this up before ;)AWS CLIBy default, aenv uses the aws CLI default profile, but of course, you can choose the profile, that you want to use, simply do:aenv -p <awscli profile name>
#or 
aenv -h 
#to see more configuration options(More details in the "Usage" section)MFAMulti-factor authentication is highly suggested!https://lmgtfy.com/?q=why+mfa+is+importantOk, all jokes aside especially for production parameters your IAM users should require MFA authentication at least for production parameters.At least in my humble opinion, this should be a "better be safe than sorry" point.Especially for your production systems!AEnv supports multiple MFA options, details in the "Usage" section, here the short overview:# Normal virtual mfa token:
aenv -t <TOKEN>

# Asks for the token during runtime:
aenv -T

# leads to an interactive token query during runtime:
$ aenv -T
$ Please enter token: 

#Yubikey Authenticator:
aenv -YTodosAdd managing mode with TerminalMenu to read specific valuesFunction to add new entriesFunction to update existing entriesFunction to delete existing entriesrefactor whole code baseAdd better permission error handling (Especially the auth part with Yubikey handling)Add check if service exists/can be read with proper error messageUpdate and correct -h / --help outputAdd regex filter for only loading specific variablesAdd regex filter to leave out variables from loadingadd -P to save a default profileUpdate initial setup instructions + console output for this(ykman + output for missing service)Option to list all available environments / services(discover/list env / list services)Check for ykman on -Y calls improve outputCurrently only the fist MFA device of any given account is used -> add mfa device selection + option for default selectioncleanup/refactor documentation / improve overall structureadd auth only mode(no env and no service name given)Add more information about container mode and necessary IAM permissionsEnhance local profile/config setup/usageHandle the region in the same way services are handledLoad multiple services at once instead of concatenating multiple aenv calls ( "aenv -s Service1 aenv -s Service2 ")Add feature for only loading certain variables to speed up loadingAdd assume role feature to support this setup more ease ->https://aws.amazon.com/de/blogs/security/enhance-programmatic-access-for-iam-users-using-yubikey-for-multi-factor-authentication/Add testingAcknowledgmentsInspired by:Gunnar Zarncke-LinkedInand his/troy gmbh's opensource credo projekt -Git CredoBug reports:Arif PEHLİVANCarlos FreundLicenseMITLinkSupport me :heart: :star: :money_with_wings:If this project provided value, and you want to give something back, you can give the repo a star or support by buying me a coffee.