DEV Community

gurucharan
gurucharan

Posted on • Edited on

Accessing kubernetes secrets in angular app,running in a Nginx Container.

The problem with K8S secrets is that, they are not directly available in the environments folder to access and unlike nodejs you don't have a process object either. So, i went with a small workaround by dumping all the variables that I need into a JSON file and accessing them from there.

I achieved this by running a python code chunk as part of a shell script , that is provided as input to ENTRYPOINT in Dockerfile. I used the same script to start my nginx web server.

This thing can be divided into 2 simple steps.
Step 1 : Dumping env into a JSON file.
Step 2 : Reading env from JSON file.

STEP 1 :
Step 1 involves making changes to the Dockerfile and adding the entrypoint script to our code.

FROM node AS base
WORKDIR /code
COPY package*.json ./
RUN  npm install    
COPY . .
ENTRYPOINT ["npm"]

FROM base AS builder
RUN npm run build

FROM alpine
RUN apk add --update nginx python3
WORKDIR /opt/app
COPY --from=builder /code/dist ./dist
COPY --from=builder /code/entrypoint.sh /opt/app/entrypoint.sh
COPY --from=builder /code/nginx/nginx.conf /etc/confd/conf.d/nginx.conf
EXPOSE 80
RUN chmod u+x /opt/app/entrypoint.sh
ENTRYPOINT ["/opt/app/entrypoint.sh"]

I'm using a multistage build,I copy dist from builder into nginx folder in the later stage of build.I also copied the entrypoint.sh script from builder and here comes the intresting part entrypoint.sh .
nginx by default creates a bunch of environment variables, which are not needed by the angular app.So, i prefixed only the variables that i need in my code with 'MYAPP_' in the K8S deployment yaml.

#!/bin/sh
env=`python3 -c 'import json, os;
env_variables={}
for key, value in iter(os.environ.items()):
  if(key.startswith("MYAPP_")):
    env_variables[key]=value
print(json.dumps(env_variables))'`
echo $env > /opt/app/dist/assets/env.json
/usr/sbin/nginx

This brings us to the end of step 1.

STEP 2 :
We begin step 2 by having all the required variables dumped into a JSON file, placed at /opt/app/dist/assets/env.json location in the container and now we have to use them in our code.
This can be done in two ways:

  1. making http request to access the file (this has a small security concern)
  2. loading JSON file directly in the code by making adding configuration in tsconfig.json.

But, both of them involve adding a service that is invoked by app.module.ts This is how the app module looks, Appservice is responsible for doing one of the two things, that I mentioned above. You can read more about APP_INITIALIZER in the angular documentation.

import { NgModule, APP_INITIALIZER } from '@angular/core';
import { AppComponent } from './app.component';
import { AppService } from './app.service';

export function init_app(appService: AppService) {
    return () => { };
}
@NgModule({
    declarations: [AppComponent],
    imports: [ ],
    providers: [
         {
            provide: APP_INITIALIZER,
            useFactory: init_app,
            multi: true,
            deps: [AppService]
        }
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

Now comes reading the variables from JSON and this involves changes in AppService, depending on what you choose from either of the ways mentioned earlier.

1st Way : This approach is not advised , when you have sensitive information like API keys or secret access keys in env as it would log them to dev console in response tab when you make http request.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
    providedIn: 'root'
})

export class AppService {

    constructor(private http: HttpClient) {
        this.load().then(data => console.log(data));
    }

    url = 'assets/env.json';
    private env: any = null;

    get settings() {
        return this.env;
    }

    public load(): Promise<any> {
        return new Promise((resolve, reject) => {
            this.http.get(this.url).subscribe((response: any) => {
                this.env = response;
                resolve(true);
            });
        });
    }

}

2nd Way: I Personally like this approach over the other because it suits all scenarios.But for this to work,you have to add "resolveJsonModule": true in the compilerOptions in the tsconfig.json.

import { Injectable } from '@angular/core';
import *  as  data from '../assets/env.json';
@Injectable({
    providedIn: 'root'
})

export class AppService {

    constructor() {
        this.load().then(data => console.log(data));
    }

    private env: any = null;

    get settings() {
        return this.env;
    }

    public load(): Promise<any> {
        return new Promise((resolve, reject) => {
            this.env = data;
            resolve(true);
        });
    }

}

Here we go, you can now access all the environment variables including K8S secrets, by injecting appService into your component and reading from appservice.env variable.

Thanks For reading,Hope you liked it .

PEACE :)

Top comments (5)

Collapse
 
praveenreddy5336 profile image
Manne Praveen Reddy

Excellent Article๐Ÿ‘Œ๐Ÿ‘Œ

Collapse
 
hhkris4u profile image
Hari

Very useful

Collapse
 
knowinmins profile image
PradeepRaj

Can you share the repo URL?
When I try to run, my docker container exits suddenly.
I don't know what I'm missing.

Collapse
 
knowinmins profile image
PradeepRaj
Collapse
 
vishakvinod profile image
vishakvinod

For the 2nd Way to work, you would need to have the file during the build. Can't it be at run time ??