Generate Angular environment.ts dynamically at the build time in AWS Code Build with AWS Parameter Store

Tharaka Madhusanka
7 min readMay 15, 2022

Hey folks ! Again with new write up, this can be considered as part II of the article ‘How to deploy Angular Web App from GitHub to AWS S3 in AWS Pipeline’. If you haven’t read it yet, read it, as this article is linked with it :)

So in this article I am going to propose a way to generate Angular environment.ts file dynamically by reading & injecting the AWS Parameter Store Keys inside the environment.ts. Why I need this approach is I want to store Application Secret Keys more securely instead using the traditional angular way. I wanted to be more GeeKy ! Lol !

So, let’s dive in :)

Prerequisites

  1. Have read my article ‘How to deploy Angular Web App from GitHub to AWS S3 in AWS Pipeline
  2. Knowledge on Angular Environment.ts file
  3. Knowledge on AWS Systems Manager and Parameter Store
  4. Familiar with NodeJS running environment

Problem

In Angular way, we use environment files to store different Application keys/EnvironmentVariables which are used inside the application. And we use different environment.ts files environment.prod.ts, environment.dev.ts etc. to store different keys or environment variables.

Let’s say that we need to store different API Subscription keys, for different environments. Then in traditional Angular way, what we can do is, create different environment files [environment.prod.ts, environment.dev.ts] and store each subscription keys.

But this is not secure, and this is a data breach as we expose pure keys as string values in the source files.

And next disadvantage what I see here is we need to use use different environment.<environment prefix>.ts files and at the build time replace the values in environment.ts with the values of environment.<environment prefix>.ts. But why can’t we use only one environment.ts file ?

Nowadays, different cloud providers provide a way to store secret keys, as AWS Systems Manager (Parameter Store), Azure Key Vault etc. And we can read those secret keys in a CI/CD pipeline in an authentic manner. By considering those facilities, I thought to derive a way to render this environment.ts dynamically, at the code build time, in AWS CI/CD pipeline, by reading Environment Variables/Keys from AWS Parameter Store. You can utilize this approach in Azure CI/CD Environment too.

Solution

will start like this :D

If you are familiar with NodeJs environment variables, you may aware that there is a global environment variable process.env which is injected at NodeJs run time.

As NodeJs document says,

The process.env property returns an object containing the user environment.

So, this solutions is mainly based on this process.env ;-)

Yes, when it comes to AWS Pipeline Environment Variables, those are bound to this process.env when the pipeline process starts.

In a NodeJS application, we can directly use this process.env and we can read different environment variables. But, in Angular environment we cannot use this process.env directly ! This is where the problem comes.

Why can’t access process.env in Angular ? In simple terms NodeJS is run time environment but Angular is not. You can Read NodeJS vs Angular for more !

But out of the Angular environment, we can utilize the support of NodeJS within the Angular project right ? Yes It is. So, I utilize this support and I use NodeJS file system module (node:fs)

So our main purpose is to render the envirnoment.ts dynamically but need to inject Environment Variables too :D

So, how I did this ? See HOW :P

How

I can brief the approach I took as below,

Step 1 — Add a separate JS file (environment-generate.js) and add program to generate the environment.ts file with the content. As this is running out of Angular but within NodeJS env, within this file I can access process.env.

Add the following code work.

function generateEnvironmentContent() {return `export const environment = {production: ${process.env.IS_PRODUCTION || false},environment: "${process.env.ENVIRONMENT || "local"}",sampleText: "${process.env.SAMPLE_PARAM_STORE || "I am from Dynamic Environment :D"}"};`}(function generateEnvironment() {const fs = require('fs');const fileName = 'environment.ts'; // you can this as hard coded name, or you can use your own unique nameconst content = generateEnvironmentContent();process.chdir(`src/environments`); // This is the directory where you created the environment file. you can use your own path, but for this I used the Angular default environment directoryfs.writeFile(fileName, content, (err) => { (err) ? console.log(err) : console.log('env is generated!') });})();
  • SAMPLE_PARAM_STORE is an Environment Variable I added in AWS parameter store.
  • ENVIRONMENT is also an Environment Variable I added in AWS CI/CD Environment Variable

Step 2 — After added the above, we can omit the file replace command in angular.json as we no longer need it :D you can set

"fileReplacements": [],

Step 3 — Now I am going to add file generating command in package.json

"scripts": {"generate-env": "node environment-generate.js",.....}

That’s all what we have to do in our project :D Next we need to do some in our AWS Code Build Pipeline. For this I use the same Code Build we created in my previous article, which I mentioned at the beginning.

Step 4 — Will go to our AWS Portal. And there Search for Systems Manager. This is the place where we use to store our secret keys. In the left menu select parameter store. Then click or Create Parameter. Here I am going to add SAMPLE_PARAM_STORE as a parameter with the value as ‘This is from my sample parameter store’.

Parameter Store

Step 5 — Now let’s add Environment Variable in AWS CodeBuild. So let’s go to our codebuild which we created last time.

Codebuild

Select the radio and then in the ‘Edit’ drop down select Environment.

Select Environment

This brings you to Environment section and there under addtional configurations you can add see ‘Environment variables’ the place where you can add Environment Variables. So here I am going to add two types of Environment Variables.

  1. SAMPLE_PARAM_STORE is read from Parameter Store.
  2. ENVIRONMENT is read from pipeline it self, as it is set as PLAINTEXT.
Set Environment Variables

After that we need to do one more thing. We need to update our buildspec commands. We need to add a command to generate environment file.

So go to your Buildspec. Add following command and update it as below,

      - echo Generate Environment File
- npm run generate-env // newly added command
- echo Env file is generated
- echo Build the project
- npm run build
- echo Finished the build project

That’s it.

I added following code some in our app.component, for the testing purpose.

import { environment } from 'src/environments/environment';.....sampleText = environment.sampleText;constructor() {console.log(this.sampleText);}

So after add the Application code changes then push it to the repo. After will run our code build and will see whether it works or not.

Whaat ! I got this error :(

Access Denied

Why this ? This is because, in AWS for each service to service communication there is a representative role and set of policies with set of permissions. So here we are trying to access System Manager’s service from Codebuild service. So we need to add right permission to Codebuild service role, to enable access to Systems Manager. For this,

Step 6 — Here role for you Codebuild is ‘codebuild-myAngularAwsCodeBuild-service-role’. you can see it above, in the exception details, after assumed-role.

Now in AWS portal search IAM (Identity and Access Management Service), and click on it. In the left menu, click on Roles. There you can see list of Roles assigned.

AWS Roles

Click on it and go inside. Then there you can see different policies attached to this role.

Policies Attached

These policies are created by default when you create the Code Build ! You can expand each policy and see what are the permission given.

Expand CodeBuildManagedSecretPolicy-… and click on edit and then switch to JSON tab, and remove the part /codebuild/ and keep it as below.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:GetParameters"
],
"Resource": "<Your resource Id>:parameter/*"
}
]
}

After Save :)

That’s all. Now you can re-run our Codebuild.

YAAY ! Build Succeeded :D

Now execute Codepipeline and deploy the Web App to S3. Run the Web Application then you can see that our Parameter Store Value is rendered ‘This is from my sample parameter store’.

Final output

The core advantage I can take by this approach is, I can avoid keeping Secret Keys, App Keys etc. away from the environment.ts file and I can securely store those in a secret key manager.

And next I don’t want to use multiple environment files for each environment and I can exclude fileReplacement step at angular build.

Find the sample project here https://github.com/TharakaMadhusanka/aws-sample-cicd

That’s all ! So folks, I hope that this would be helpful in some aspect. If you enjoy this write up, then Share, Clap and give you valuable feed back and suggestions. But don’t forget to,

Keep in touch, will come up with a new write up !

Stay Safe !

--

--