mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
Rebuild Credential Handling
(scoped to AWS)
Previously you had a number of options, including legacy options for loading credentials. Given the 0.x=>1.x change, we can drop a lot of the old approaches. This PR attempts to bring all the good things.
The options for loading credentials are as follows:
1. define credentials on serverless.yml=>service.provider.credentials = { accessKeyId: 'accessKeyId', secretAccessKey: 'secretAccessKey', sessionToken: 'sessionToken' }
2. define a profile from which to get credentials on serverless.yml=>service.provider.profile = 'profile-name' (all profiles loaded using AWS.SharedIniFileCredentials, see http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SharedIniFileCredentials.html)
3. define credentials for all stages using the standard AWS environment variables (see http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/EnvironmentCredentials.html)
4. define a profile for all stages using the environment variable AWS_PROFILE
5. define credentials for each stage using the standard AWS environment variables with the STAGE name inserted (e.g. stage='test', envVarName='AWS_TEST_*')
6. define a profile for each stage using an environment variable `AWS_${stageName.toUpperCase()}_PROFILE`
If credentials/profiles are declared in multiple ways, the later cases will override the former.
These use cases previously covered all user requirements but the current implemenation allows for an expansion of mechanisms if more mechanisms are desirable.
This commit is contained in:
parent
a06f4f0645
commit
2cfd611329
@ -69,37 +69,132 @@ Default output format [None]: ENTER
|
||||
|
||||
Credentials are stored in INI format in `~/.aws/credentials`, which you can edit directly if needed. Read more about that file in the [AWS documentation](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-config-files)
|
||||
|
||||
You can even set up different profiles for different accounts, which can be used by Serverless as well. To specify a default profile to use, you can add a `profile` setting to your `provider` configuration in `serverless.yml`:
|
||||
You can even set up different profiles for different accounts, which can be used by Serverless as well.
|
||||
|
||||
#### Specifying Credentials/Profiles to Serverless
|
||||
|
||||
You can specify either credentials or a profile. Each of these can be provided by altering your serverless.yml or your system's environment variables. Each can be specified for all stages or you can specify stage specific credentials. Using variables in your serverless.yml, you could implement more complex credential selection capabilities.
|
||||
|
||||
|
||||
One set of credentials for all stages using serverless.yml
|
||||
```yml
|
||||
service: new-service
|
||||
provider:
|
||||
name: aws
|
||||
runtime: nodejs4.3
|
||||
stage: dev
|
||||
profile: devProfile
|
||||
credentials:
|
||||
accessKeyId: YOUR_ACCESS_KEY
|
||||
secretAccessKey: YOUR_SECRET_KEY
|
||||
```
|
||||
|
||||
##### Per Stage Profiles
|
||||
|
||||
As an advanced use-case, you can deploy different stages to different accounts by using different profiles per stage. In order to use different profiles per stage, you must leverage [variables](../01-guide/08-serverless-variables.md) and the provider profile setting.
|
||||
|
||||
This example `serverless.yml` snippet will load the profile depending upon the stage specified in the command line options (or default to 'dev' if unspecified);
|
||||
|
||||
A set of credentials for each stage using serverless.yml
|
||||
```yml
|
||||
service: new-service
|
||||
vars:
|
||||
test:
|
||||
credentials:
|
||||
accessKeyId: YOUR_ACCESS_KEY_FOR_TEST
|
||||
secretAccessKey: YOUR_SECRET_KEY_FOR_TEST
|
||||
prod:
|
||||
credentials:
|
||||
accessKeyId: YOUR_ACCESS_KEY_FOR_PROD
|
||||
secretAccessKey: YOUR_SECRET_KEY_FOR_PROD
|
||||
provider:
|
||||
name: aws
|
||||
runtime: nodejs4.3
|
||||
stage: ${opt:stage, self:custom.defaultStage}
|
||||
profile: ${self:custom.profiles.${self:provider.stage}}
|
||||
custom:
|
||||
defaultStage: dev
|
||||
profiles:
|
||||
dev: devProfile
|
||||
prod: prodProfile
|
||||
credentials: ${self:vars.{opt:stage}.credentials}
|
||||
```
|
||||
|
||||
One profile for all stages using serverless.yml
|
||||
```yml
|
||||
provider:
|
||||
profile: your-profile
|
||||
```
|
||||
|
||||
A profile for each stage using serverless.yml
|
||||
```yml
|
||||
vars:
|
||||
test:
|
||||
profile: your-profile-for-test
|
||||
prod:
|
||||
profile: your-profile-for-prod
|
||||
provider:
|
||||
profile: ${self:vars.{opt:stage}.profile}
|
||||
```
|
||||
|
||||
One set of credentials for all stages using environment variables
|
||||
```bash
|
||||
export AWS_ACCESS_KEY_ID=<key>
|
||||
export AWS_SECRET_ACCESS_KEY=<secret>
|
||||
export AWS_SESSION_TOKEN=<token>
|
||||
serverless <...>
|
||||
```
|
||||
|
||||
A set of credentials for each stage using environment variables
|
||||
```bash
|
||||
export AWS_TEST_ACCESS_KEY_ID=<key>
|
||||
export AWS_TEST_SECRET_ACCESS_KEY=<secret>
|
||||
export AWS_TEST_SESSION_TOKEN=<token>
|
||||
|
||||
export AWS_PROD_ACCESS_KEY_ID=<key>
|
||||
export AWS_PROD_SECRET_ACCESS_KEY=<secret>
|
||||
export AWS_PROD_SESSION_TOKEN=<token>
|
||||
|
||||
serverless <...>
|
||||
```
|
||||
|
||||
A profile for all stages using environment variables
|
||||
```bash
|
||||
export AWS_PROFILE=<profile>
|
||||
serverless <...>
|
||||
```
|
||||
|
||||
A profile for each stage using environment variables
|
||||
```bash
|
||||
export AWS_TEST_PROFILE=<profile>
|
||||
|
||||
export AWS_PROD_PROFILE=<profile>
|
||||
|
||||
serverless <...>
|
||||
```
|
||||
|
||||
#### Credential & Profile Overriding
|
||||
|
||||
Sometimes you want to be able to specify a default but to override that default for a special case. This is possible with credentials and profiles in Serverless. You may specify credentials and profiles in various forms. The serverless.yml has the lowest priority and environment variables used for all stages will override values set in serverless.yml. Environment variables that are specific to a stage have the highest priority and will override both broad environment variables as well as serverless.yml. Profile provided credentials will override credentials provided in piece-meal from otherwise equivalent credential sources. A priority listing follows.
|
||||
|
||||
severless.yml credentials < serverless.yml profile credentials < all-stages environment credentials < all stages environment profile credentials < stage-specific environment credentials < stage-specific environment profile credentials
|
||||
|
||||
A default set of `prod` credentials to use overriden by stage specific credentials
|
||||
```bash
|
||||
export AWS_ACCESS_KEY_ID=<key>
|
||||
export AWS_SECRET_ACCESS_KEY=<secret>
|
||||
export AWS_SESSION_TOKEN=<token>
|
||||
|
||||
export AWS_PROD_ACCESS_KEY_ID=<prod-key>
|
||||
export AWS_PROD_SECRET_ACCESS_KEY=<prod-secret>
|
||||
export AWS_PROD_SESSION_TOKEN=<prod-token>
|
||||
|
||||
serverless <...>
|
||||
```
|
||||
|
||||
A default profile to use overriden by a `prod` specific profile
|
||||
```bash
|
||||
export AWS_PROFILE=<profile>
|
||||
|
||||
export AWS_PROD_PROFILE=<profile>
|
||||
|
||||
serverless <...>
|
||||
```
|
||||
|
||||
A default profile declared in serverless.yml overridden by a `prod` specific environment variable profile
|
||||
```yml
|
||||
provider:
|
||||
profile: your-profile
|
||||
```
|
||||
```bash
|
||||
export AWS_PROD_ACCESS_KEY_ID=<prod-key>
|
||||
export AWS_PROD_SECRET_ACCESS_KEY=<prod-secret>
|
||||
export AWS_PROD_SESSION_TOKEN=<prod-token>
|
||||
|
||||
serverless <...>
|
||||
```
|
||||
|
||||
Et cetera
|
||||
|
||||
## Conclusion
|
||||
|
||||
With the account setup in place Serverless is now able to create and manage resources on our behalf.
|
||||
|
||||
@ -5,6 +5,71 @@ const HttpsProxyAgent = require('https-proxy-agent');
|
||||
const url = require('url');
|
||||
const AWS = require('aws-sdk');
|
||||
|
||||
const impl = {
|
||||
/**
|
||||
* Add credentials, if present, from the given credentials configuration
|
||||
* @param credentials The credentials to add credentials configuration to
|
||||
* @param config The credentials configuration
|
||||
*/
|
||||
addCredentials: (credentials, config) => {
|
||||
if (credentials &&
|
||||
config &&
|
||||
config.accessKeyId &&
|
||||
config.accessKeyId !== 'undefined' &&
|
||||
config.secretAccessKey &&
|
||||
config.secretAccessKey !== 'undefined') {
|
||||
if (config.accessKeyId) {
|
||||
credentials.accessKeyId = config.accessKeyId; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
if (config.secretAccessKey) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
credentials.secretAccessKey = config.secretAccessKey;
|
||||
}
|
||||
if (config.sessionToken) {
|
||||
credentials.sessionToken = config.sessionToken; // eslint-disable-line no-param-reassign
|
||||
} else if (credentials.sessionToken) {
|
||||
delete credentials.sessionToken; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add credentials, if present, from the environment
|
||||
* @param credentials The credentials to add environment credentials to
|
||||
* @param prefix The environment variable prefix to use in extracting credentials
|
||||
*/
|
||||
addEnvironmentCredentials: (credentials, prefix) => {
|
||||
if (prefix) {
|
||||
const environmentCredentials = new AWS.EnvironmentCredentials(prefix);
|
||||
impl.addCredentials(credentials, environmentCredentials);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add credentials from a profile, if the profile exists
|
||||
* @param credentials The credentials to add profile credentials to
|
||||
* @param prefix The prefix to the profile environment variable
|
||||
*/
|
||||
addProfileCredentials: (credentials, profile) => {
|
||||
if (profile) {
|
||||
const profileCredentials = new AWS.SharedIniFileCredentials({ profile });
|
||||
if (Object.keys(profileCredentials).length) {
|
||||
credentials.profile = profile; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
impl.addCredentials(credentials, profileCredentials);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add credentials, if present, from a profile that is specified within the environment
|
||||
* @param credentials The prefix of the profile's declaration in the environment
|
||||
* @param prefix The prefix for the environment variable
|
||||
*/
|
||||
addEnvironmentProfile: (credentials, prefix) => {
|
||||
if (prefix) {
|
||||
const profile = process.env[`${prefix}_PROFILE`];
|
||||
impl.addProfileCredentials(credentials, profile);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
class SDK {
|
||||
constructor(serverless) {
|
||||
// Defaults
|
||||
@ -33,7 +98,7 @@ class SDK {
|
||||
|
||||
request(service, method, params, stage, region) {
|
||||
const that = this;
|
||||
const credentials = that.getCredentials(region);
|
||||
const credentials = that.getCredentials(stage, region);
|
||||
const persistentRequest = (f) => new BbPromise((resolve, reject) => {
|
||||
const doCall = () => {
|
||||
f()
|
||||
@ -78,15 +143,30 @@ class SDK {
|
||||
});
|
||||
}
|
||||
|
||||
getCredentials(region) {
|
||||
const credentials = { region };
|
||||
const profile = this.serverless.service.provider.profile;
|
||||
/**
|
||||
* Fetch credentials directly or using a profile from serverless yml configuration or from the
|
||||
* well known environment variables
|
||||
* @param stage
|
||||
* @param region
|
||||
* @returns {{region: *}}
|
||||
*/
|
||||
getCredentials(stage, region) {
|
||||
const ret = { region };
|
||||
const credentials = {};
|
||||
const stageUpper = stage ? stage.toUpperCase() : null;
|
||||
|
||||
if (typeof profile !== 'undefined' && profile) {
|
||||
credentials.credentials = new AWS.SharedIniFileCredentials({ profile });
|
||||
// add specified credentials, overriding with more specific declarations
|
||||
impl.addCredentials(credentials, this.serverless.service.provider.credentials); // config creds
|
||||
impl.addProfileCredentials(credentials, this.serverless.service.provider.profile);
|
||||
impl.addEnvironmentCredentials(credentials, 'AWS'); // creds for all stages
|
||||
impl.addEnvironmentProfile(credentials, 'AWS');
|
||||
impl.addEnvironmentCredentials(credentials, `AWS_${stageUpper}`); // stage specific creds
|
||||
impl.addEnvironmentProfile(credentials, `AWS_${stageUpper}`);
|
||||
|
||||
if (Object.keys(credentials).length) {
|
||||
ret.credentials = credentials;
|
||||
}
|
||||
|
||||
return credentials;
|
||||
return ret;
|
||||
}
|
||||
|
||||
getServerlessDeploymentBucketName(stage, region) {
|
||||
|
||||
@ -88,11 +88,133 @@ describe('AWS SDK', () => {
|
||||
it('should set region for credentials', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
const credentials = awsSdk.getCredentials('testregion');
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
expect(credentials.region).to.equal('testregion');
|
||||
});
|
||||
|
||||
it('should get credentials from provider', () => {
|
||||
it('should not set credentials if credentials are undefined', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
serverless.service.provider.credentials = undefined;
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
expect(credentials).to.eql({ region: 'testregion' });
|
||||
});
|
||||
|
||||
it('should not set credentials if credentials is the empty string', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
serverless.service.provider.credentials = '';
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
expect(credentials).to.eql({ region: 'testregion' });
|
||||
});
|
||||
|
||||
it('should not set credentials if credentials is an empty object', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
serverless.service.provider.credentials = {};
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
expect(credentials).to.eql({ region: 'testregion' });
|
||||
});
|
||||
|
||||
it('should not set credentials if credentials has undefined values', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
serverless.service.provider.credentials = {
|
||||
accessKeyId: undefined,
|
||||
secretAccessKey: undefined,
|
||||
sessionToken: undefined,
|
||||
};
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
expect(credentials).to.eql({ region: 'testregion' });
|
||||
});
|
||||
|
||||
it('should not set credentials if credentials has empty string values', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
serverless.service.provider.credentials = {
|
||||
accessKeyId: '',
|
||||
secretAccessKey: '',
|
||||
sessionToken: '',
|
||||
};
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
expect(credentials).to.eql({ region: 'testregion' });
|
||||
});
|
||||
|
||||
it('should get credentials from provider declared credentials', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
serverless.service.provider.credentials = {
|
||||
accessKeyId: 'accessKeyId',
|
||||
secretAccessKey: 'secretAccessKey',
|
||||
sessionToken: 'sessionToken',
|
||||
};
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
expect(credentials.credentials).to.deep.eql(serverless.service.provider.credentials);
|
||||
});
|
||||
|
||||
it('should get credentials from environment declared for-all-stages credentials', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
const prevVal = {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
sessionToken: process.env.AWS_SESSION_TOKEN,
|
||||
};
|
||||
const testVal = {
|
||||
accessKeyId: 'accessKeyId',
|
||||
secretAccessKey: 'secretAccessKey',
|
||||
sessionToken: 'sessionToken',
|
||||
};
|
||||
process.env.AWS_ACCESS_KEY_ID = testVal.accessKeyId;
|
||||
process.env.AWS_SECRET_ACCESS_KEY = testVal.secretAccessKey;
|
||||
process.env.AWS_SESSION_TOKEN = testVal.sessionToken;
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
process.env.AWS_ACCESS_KEY_ID = prevVal.accessKeyId;
|
||||
process.env.AWS_SECRET_ACCESS_KEY = prevVal.secretAccessKey;
|
||||
process.env.AWS_SESSION_TOKEN = prevVal.sessionToken;
|
||||
expect(credentials.credentials).to.deep.eql(testVal);
|
||||
});
|
||||
|
||||
it('should get credentials from environment declared stage specific credentials', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
const prevVal = {
|
||||
accessKeyId: process.env.AWS_TESTSTAGE_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_TESTSTAGE_SECRET_ACCESS_KEY,
|
||||
sessionToken: process.env.AWS_TESTSTAGE_SESSION_TOKEN,
|
||||
};
|
||||
const testVal = {
|
||||
accessKeyId: 'accessKeyId',
|
||||
secretAccessKey: 'secretAccessKey',
|
||||
sessionToken: 'sessionToken',
|
||||
};
|
||||
process.env.AWS_TESTSTAGE_ACCESS_KEY_ID = testVal.accessKeyId;
|
||||
process.env.AWS_TESTSTAGE_SECRET_ACCESS_KEY = testVal.secretAccessKey;
|
||||
process.env.AWS_TESTSTAGE_SESSION_TOKEN = testVal.sessionToken;
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
process.env.AWS_TESTSTAGE_ACCESS_KEY_ID = prevVal.accessKeyId;
|
||||
process.env.AWS_TESTSTAGE_SECRET_ACCESS_KEY = prevVal.secretAccessKey;
|
||||
process.env.AWS_TESTSTAGE_SESSION_TOKEN = prevVal.sessionToken;
|
||||
expect(credentials.credentials).to.deep.eql(testVal);
|
||||
});
|
||||
|
||||
it('should not set credentials if profile is not set', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
serverless.service.provider.profile = undefined;
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
expect(credentials).to.eql({ region: 'testregion' });
|
||||
});
|
||||
|
||||
it('should not set credentials if empty profile is set', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
serverless.service.provider.profile = '';
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
expect(credentials).to.eql({ region: 'testregion' });
|
||||
});
|
||||
|
||||
it('should get credentials from provider declared profile', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
serverless.service.provider.profile = 'notDefault';
|
||||
@ -100,20 +222,24 @@ describe('AWS SDK', () => {
|
||||
expect(credentials.credentials.profile).to.equal('notDefault');
|
||||
});
|
||||
|
||||
it('should not set credentials if empty profile is set', () => {
|
||||
it('should get credentials from environment declared for-all-stages profile', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
serverless.service.provider.profile = '';
|
||||
const credentials = awsSdk.getCredentials('testregion');
|
||||
expect(credentials).to.eql({ region: 'testregion' });
|
||||
const prevVal = process.env.AWS_PROFILE;
|
||||
process.env.AWS_PROFILE = 'notDefault';
|
||||
const credentials = awsSdk.getCredentials();
|
||||
process.env.AWS_PROFILE = prevVal;
|
||||
expect(credentials.credentials.profile).to.equal('notDefault');
|
||||
});
|
||||
|
||||
it('should not set credentials if profile is not set', () => {
|
||||
it('should get credentials from environment declared stage-specific profile', () => {
|
||||
const serverless = new Serverless();
|
||||
const awsSdk = new AwsSdk(serverless);
|
||||
serverless.service.provider.profile = undefined;
|
||||
const credentials = awsSdk.getCredentials('testregion');
|
||||
expect(credentials).to.eql({ region: 'testregion' });
|
||||
const prevVal = process.env.AWS_TESTSTAGE_PROFILE;
|
||||
process.env.AWS_TESTSTAGE_PROFILE = 'notDefault';
|
||||
const credentials = awsSdk.getCredentials('teststage', 'testregion');
|
||||
process.env.AWS_TESTSTAGE_PROFILE = prevVal;
|
||||
expect(credentials.credentials.profile).to.equal('notDefault');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user