Open dhmacs opened 4 years ago
@macs91 Use next's build time configuration . This is the recommended approach for managing environment config. in your pages.
@danielcondemarin I tried it, but I see that it leaks env variables to the client... Given that I intend to store secrets I can't use that 😬
@danielcondemarin I tried it, but I see that it leaks env variables to the client... Given that I intend to store secrets I can't use that 😬
Ohh I see. Sorry I wasn't entirely clear how build time env. works. Sounds like there is scope then for injecting env. variables via serverless.yaml. Reopening ...
@macs91 Could you share how you're accessing the env. config. in the page? i.e. on getInitialProps
I'm guessing?
Yes, the secret will be accessed from getInitialProps
.. I dug a bit more, and it seems that only the env variables that are explicitly referenced on client side code are visible in the bundle, so basically env variables are not exposed unless they are explicitly included in client side code?
So If I don't want the env variable to get included in the bundle maybe it's enough to use it under this condition:
if (!process.browser) {
console.log("my secret", process.env.SECRET);
}
Ok so maybe this way it can work, but we have to be careful to make sure the env variable are not referenced inside the client bundle.
@macs91 Sounds incredibly easy to leak the secure config. by accident., so I'll be adding support for runtime only env. configuration via a serverless.yml
input. Stay tuned 😄
Yep I thought that too 😅 When env variables are set only on the lambda it's much harder to leak secrets by mistake. Thanks 👍
@macs91 Found out today Lambda@Edge doesn't support environment variables :/
Hopefully this will be supported / available soon! But for now you'll have to workaround it by using the build time config. with care. I'll keep the issue opened.
Bummer! I didn't know that.. Hopefully they will announce support at re:Invent 😁
Bummer! I didn't know that.. Hopefully they will announce support at re:Invent 😁
That's what I'm hoping. All I can do for now is ask for it 🙂
Oh no... is there still no way to do this? I used NextJS and this component to handle a simple contact form but I'd like to not leak the email address the form sends the submission to.... Anyway to handle this? If not then this has practically killed my app with this dead in the water :(
@gbwashleybrown Did you have a look at build.env
? https://github.com/danielcondemarin/serverless-next.js#inputs
It uses next's build time env variables as documented in https://nextjs.org/docs/api-reference/next.config.js/environment-variables
@danielcondemarin According to the conversation further up this post, doing that will leak values into the client? So I would prefer not to do that as I don't need to access them in the client.
My serverless function loops trough an array of email addresses and picks one to send the contact form submission depending on some stuff. So being able to access all the email addresses on the server side only without exposing them to the client is key for me to be able to use this in a production environment.😕
@gbwashleybrown The safest way to do this is to create a handler in a /api
route then POST/GET to that handler to loop your array etc, /api
routes are not exposed to the client as they are a simple request/response and always run on the server side, you may find using this that you don't need process.env vars at all,
Take a look at https://nextjs.org/docs/api-routes/introduction
And also a rest example here: https://github.com/zeit/next.js/tree/canary/examples/api-routes-rest
@Podders Thank you.
I think I may have misunderstood how Next is bundled.
The email array and sendmail method is within /pages/api/sendmail.js
and I was under the impression that /api/*
is exposed to the client.
For as long as server-side functions (/pages/api/*
) files aren't exposed then I should be ok. 😁
I found this issue when I had a lambda function (created with '@serverless/backend') that would be deployed along side my next.js app and I wanted to expose an environment variable to the next.js app during build time that was equal to the url of the newly deployed lambda function.
my solution was as follows
// serverless.yml
api:
component: '@serverless/backend'
inputs:
code:
src: api/src
env:
dbName: ${database.name}
dbRegion: ${database.region}
frontend:
component: "serverless-next.js"
inputs:
domain: "yourdomain.com"
build:
env:
API_URL: ${api.url}
// next.config.js
module.exports = {
target: "serverless",
env: {
API_URL: process.env.API_URL
}
}
// pages/index.js
export async function getStaticProps() {
const API_URL = process.env.API_URL
return {
props: {
API_URL
}
}
}
[CORRECTION] Adding my env variables in the next.config.js file suffuced:
module.exports = {
env: {
//env variables here
}
}
What we ended up doing was using AWS Secrets Manager, since you can request from Lambda@Edge. Downside is having to make a region-specific request to get the secrets from the Lambda, but works as a stop-gap for now.
@gbwashleybrown Did you have a look at
build.env
? https://github.com/danielcondemarin/serverless-next.js#inputs It uses next's build time env variables as documented in https://nextjs.org/docs/api-reference/next.config.js/environment-variables
Is this working? I have been trying this method and it doesn't seem like build.env does anything at all...
Maybe I am misunderstanding how this works, so far in order to set a env variable I used:
appName:
component: '@sls-next/serverless-component@1.17.0-alpha.6'
build:
env:
NEXTAUTH_URL: https://example.cloudfront.net
Say NEXTAUTH_URL
thought is not being set at all, as in for this specific example is not fetched by next-auth, what am I missing?
EDIT:
Of course the following would work:
// next.config.js
module.exports = {
env: {
NEXTAUTH_URL: https://example.cloudfront.net
}
}
But wouldn't that both expose the env variable to the bundle and also to VCS? How can we avoid that?
@MelMacaluso I had the same problem. I am also using next-auth. Look at the solution by @bobrown101. That worked for me. build.env
must go under inputs
. So the documentation is slightly off.
@andrew310 thanks mate! Will try out and let you know 🙏
Actually the only thing that works for me is defining the envs in the next.config.js which is not ideal as is commited...if anyone has some other solutions would be greatly appreciated 🙏
Actually I am facing issue to maintain the deployment process with the same build which I created for my DEV environment. As you know we need to deploy the same code on different environments(DEV, UAT & PROD), and have different endpoints for each environment. So I want to deploy the build which I created for DEV environment to UAT & PROD, but how can I change the end-point without making the new build of nextjs code?
Below is how I was able to include environment variables without needing to commit them to source (assumes you're using GitHub actions to deploy your app).
Tradeoffs of this approach:
next.config.js
)This will be at https://github.com/<username>/<project>/settings/secrets
Assuming your GitHub workflow looks something like this one, include your desired environment variables in the step where the app is built and deployed
- name: Deploy to AWS
run: npx serverless
env:
MY_SECRET_API_KEY: ${{ secrets.MY_SECRET_API_KEY }}
build.env
(again assumes a structure somewhat like this one))myApp:
component: serverless-next.js
inputs:
...
build:
env:
MY_SECRET_API_KEY: ${env.MY_SECRET_API_KEY}
next.config.js
, add the variables you want includedmodule.exports = {
target: 'serverless',
env: {
MY_SECRET_API_KEY: process.env.MY_SECRET_API_KEY
}
};
... and that's pretty much it. This isn't a great solution because of the tradeoffs already mentioned, and if you have the time to sink into figuring out how to get AWS Secret Manager working, that would probably be a better workaround. It's unfortunate that there's no easy way to configure serverless next.js without committing your env vars to source as that's an anti-pattern.
Just a note from me. I was also struggling with the same issue and @murtyjones solution just did not work for me. The problem seems to be step 3 where I needed to change
myApp:
component: '@sls-next/serverless-component@1.18.0'
inputs:
...
build:
env:
MY_SECRET_API_KEY: ${env.MY_SECRET_API_KEY}
to
myApp:
component: '@sls-next/serverless-component@1.18.0'
inputs:
...
env:
MY_SECRET_API_KEY: ${env.MY_SECRET_API_KEY}
So I needed to remove build nesting for env in serverless.yml file.
Best regards
@murtyjones Your comment "figuring out how to get AWS Secret Manager working, that would probably be a better workaround," -- Makes me believe I am pushing into a rope trying to use AWS Secrets Manager with nextjs to handle both oAuth2 secrets and DB passwords. I think I should stop trying an concede to the anti-pattern...
@murtyjones Your comment "figuring out how to get AWS Secret Manager working, that would probably be a better workaround," -- Makes me believe I am pushing into a rope trying to use AWS Secrets Manager with nextjs to handle both oAuth2 secrets and DB passwords. I think I should stop trying an concede to the anti-pattern...
yeah, it's definitely doable but it takes some effort. i've also played around with AWS Systems Manager, which looks like it might be easier to use with AWS Lambda than the Secrets Manager, but I haven't tried that yet so not sure if there are any gotchas.
this is one of those things where someone who publishes a blog post guide will rake in the traffic 😄
Environment variables are almost always needed with the backend (secret keys specifically), so everyone is going to be facing this same problem - which I suspect is a heck of a lot people? Why can't we just make this none Lambda@Edge and just use normal Lambda instead? I would to help, if I can, but I'm not particularly great at writing things like this and don't really know the codebase too well.
With that said, I'm having to store secrets in /api/*
files directly, rather than an .env
file. This is fine for now, but obviously not ideal as it gets saved in VCS and as you know, it's just a really insecure way of storing your code.
The approach I've gone with for a multi-env i18n Next.js site is to set a BUILD_ENV
variable which is used to determine which environment variables to load in. So my serverless.yml
file looks like:
myNextApp:
component: '@sls-next/serverless-component@1.19.0-alpha.0'
build:
env:
BUILD_ENV: ${env.BUILD_ENV}
inputs:
timeout: 30
build:
postBuildCommands: ['node serverless-post-build.js', 'node sitemap.xml.js']
memory: 1024
Then in the root of the repo I've got a environments
folder containing .env.localhost
, .env.develop
and .env.main
:
These files look something like:
NEXT_PUBLIC_BUILD_ENV=develop
NEXT_PUBLIC_AUTH_DOMAIN=auth.staging.yoursite.com
NEXT_PUBLIC_SITE_URL=https://yoursite.cloudfront.net/
In my next.config.js
file I include the necessaray environment variables with dotenv
:
require('dotenv').config({
path: `environments/.env.${process.env.BUILD_ENV || 'localhost'}`,
});
If you've got additional post-build scripts as I have then you'll need to include the above snippet to i.e in sitemap.xml.js
Then all that is left to do is run:
BUILD_ENV=develop npx serverless
The key point in this issue is to get environment variables from environment not from .env files. Using tools like chamber
you can securely store them in AWS Parameter Store, or similar secure remote storage, so you do not need to commit secrets any more to repository.
Also works for CI/CD - you just use AWS access secrets + chamber
to get all required env vars without having a need to configure them one by one everythere.
Just a note from me. I was also struggling with the same issue and @murtyjones solution just did not work for me. The problem seems to be step 3 where I needed to change
myApp: component: '@sls-next/serverless-component@1.18.0' inputs: ... build: env: MY_SECRET_API_KEY: ${env.MY_SECRET_API_KEY}
to
myApp: component: '@sls-next/serverless-component@1.18.0' inputs: ... env: MY_SECRET_API_KEY: ${env.MY_SECRET_API_KEY}
So I needed to remove build nesting for env in serverless.yml file.
Best regards
@PaulKushch im still having issues even with this that nothing shows up in my lambda env? Currently struggling with this env issue over in #1010
Do your vars show up in the lambda config? i feel like im missing something obious
I tried adding the env
varaibles to the apiEdgeLambdaInput
https://github.com/serverless-nextjs/serverless-next.js/blob/master/packages/serverless-components/nextjs-component/src/component.ts#L479-L503
name: readLambdaInputValue("name", "apiLambda", undefined) as
| string
| undefined,
env: inputs.env?.apiLambda
};
But I got the error InvalidLambdaFunctionAssociation: The function cannot have environment variables.
@macs91 Found out today Lambda@Edge doesn't support environment variables :/
Hopefully this will be supported / available soon! But for now you'll have to workaround it by using the build time config. with care. I'll keep the issue opened.
We should add @murtyjones steps to the official documentation. I spent a day to figure this out. It is important that you follow all 4 steps. And what is very important to point out that you have to use a .
instead of :
when accessing env variables in the serverless.yaml
(e.g. ${env.SECRET}
instead of ${env:SECRET}
)
I came to this thread as I was having issues getting the environment variable for NextAuth (NEXTAUTH_URL) to be picked up in my deployed distribution.
Just in case it's useful, I saw a question about how to get values from AWS Secrets Manager. I thought I'd share this code I used in my Next.js App to get values from AWS Secrets Manager (apologies if this taking the thread off topic and if the code isn't very good):
import { AWSError, SSM } from 'aws-sdk'
import SecretsManager, { GetSecretValueResponse } from 'aws-sdk/clients/secretsmanager'
import { PromiseResult } from 'aws-sdk/lib/request'
const client = new SecretsManager({ region: 'us-east-1' })
function getAwsSecret(
secretName: string
): Promise<PromiseResult<GetSecretValueResponse, AWSError>> {
return client.getSecretValue({ SecretId: secretName }).promise()
}
export const getAwsSecretAsync = async (
secretName: string
): Promise<PromiseResult<GetSecretValueResponse, AWSError>> => {
try {
const response = await getAwsSecret(secretName)
return response
} catch (error) {
console.error('Error occurred while retrieving AWS secret')
console.error(error)
throw new Error(error)
}
}
For completeness, I use this with AWS Parameter Store
const getParameterWorker = async (name: string, decrypt: boolean): Promise<string> => {
const ssm = new SSM({ region: 'us-east-1' })
const result = await ssm.getParameter({ Name: name, WithDecryption: decrypt }).promise()
if (result?.Parameter?.Value !== undefined) return result.Parameter.Value
throw Error('Could not retrieve paramater for: ' + name)
}
export const getParameter = async (name: string): Promise<string> => {
return getParameterWorker(name, true)
}
The IAM role associated with the distribution of the API Lambda then needs permission to get the value, with a policy along the lines of:
{
"Sid": "VisualEditor2",
"Effect": "Allow",
"Action": [
"ssm:GetParametersByPath",
"ssm:GetParameters",
"ssm:GetParameter"
],
"Resource": [
"arn:aws:ssm:*:<account-number>:parameter/<parameter-name>"
]
}
In our CI/CD (Gitlab) we do this sort of thing to get parameters from SSM (with AWS Access Key ID and AWS Secret Access Key values in out Gitlab Environment variables) using an IAM account in one AWS account with permissions to assume roles in other accounts:
default:
...
before_script:
- mkdir ~/.aws && touch ~/.aws/config && touch ~/.aws/credentials
- echo -e "[profile dev]\nregion=us-east-1 \nrole_arn=arn:aws:iam::<account-number>:role/DevAccount \nsource_profile = master" >> ~/.aws/config
- echo -e "[profile test]\nregion=us-east-1 \nrole_arn=arn:aws:iam::<account-number>:role/TestAccount \nsource_profile = master" >> ~/.aws/config
- echo -e "[profile prod]\nregion=us-east-1 \nrole_arn=arn:aws:iam::<account-number>:role/ProdAccount \nsource_profile = master" >> ~/.aws/config
- echo -e "[master]\naws_access_key_id=$CI_MASTER_AWS_ACCESS_KEY_ID\naws_secret_access_key=$CI_MASTER_AWS_SECRET_ACCESS_KEY" > ~/.aws/credentials
- export AWS_CONFIG_FILE=~/.aws/config
- export AWS_SHARED_CREDENTIALS_FILE=~/.aws/credentials
- export AWS_SDK_LOAD_CONFIG=true
...
deploy dev:
...
variables:
AWS_PROFILE: 'dev'
...
script:
- export API_KEY=$(aws ssm get-parameter --region us-east-1 --name /secret/api/key --with-decryption --query Parameter.Value --output text)
...
Just a note from me. I was also struggling with the same issue and @murtyjones solution just did not work for me. The problem seems to be step 3 where I needed to change
myApp: component: '@sls-next/serverless-component@1.18.0' inputs: ... build: env: MY_SECRET_API_KEY: ${env.MY_SECRET_API_KEY}
to
myApp: component: '@sls-next/serverless-component@1.18.0' inputs: ... env: MY_SECRET_API_KEY: ${env.MY_SECRET_API_KEY}
So I needed to remove build nesting for env in serverless.yml file. Best regards
@PaulKushch im still having issues even with this that nothing shows up in my lambda env? Currently struggling with this env issue over in #1010
Do your vars show up in the lambda config? i feel like im missing something obious
same problem here. i wanna set value to lambda environment. like a TimeZone
Would this work? I tried this on serverless@3.17.0 but not sure if it is exposed to the client by any chance. My local machine's OS is ubuntu 20.04
export MY_VARIABLE=value
(shell command)
In your code use
process.env.MY_VARIABLE
Then deploy from local in your shell
components-v1
or (depending on serverless version) serverless
I assume it is not exposed since here on the documentation they might be using this method in the "Getting Started" section for their AWS credentials.
How can I pass environment variables? I would like to pass some api key and secrets both to api and frontend lambdas, how can I do that?
I tried like this but it doesn't work: