例のごとくApexからSTSを触ってみました。
STSとは
STSはAWS Security Token ServiceというAWSのサービスで[IAMユーザを持たないアプリに対してアクセス権限を与える]サービスになります。具体的には以下の用途で利用されます
- 既存のLDAP, ADを利用した連携
- facebook, google等のOAuth(Open ID)アプリのWeb ID連携
- SAMLアサーションを利用した連携
- 別のAWS環境(AWSアカウント自体が異なる)へのクロスアカウントアクセス
開発環境のAWSアカウントと本番環境のAWSアカウントを分けている場合は、クロスアカウントアクセスが有効で、本番環境のクレデンシャル無しに本番環境のリソースにアクセス出来ます。
詳細なユースケースはこちらから↓ http://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/WorkingWithRoles.html
ちなみにIAMのRoleっていう機能はイコールSTSなので、STSはIAMユーザを持たないアプリに対してRoleを付与するためのサービスになります。
Apexから叩く
で、以下がApexのサンプルクラス/**
* STSコモンクラス
*/
public with sharing class STSCommon {
private final String SERVICE = 'sts';
private final String REGION = 'us-east-1';
/**
* Access Key Id
*/
private String AWSAccessKeyId = 'Input Your AWS Access Key Is';
/**
* Access Key Secret
*/
private String AWSAccessKeySecret = 'AWS Secret Access Key';
public String assumeRole(
String roleArn,
String roleSessionName
) {
Map<String, String> params = new Map<String, String>{
};
List<String> signedHeaders = new List<String>{
'host', 'x-amz-date'
};
DateTime dt = DateTime.now();
String credentialScope = dt.formatGmt('YYYYMMdd') + '/' +
REGION + '/' + SERVICE + '/aws4_request';
String httpBody = Utility.getParam(new Map<String, String>{
'RoleArn' => roleArn,
'RoleSessionName' => roleSessionName,
'Version' => '2011-06-15',
'Action' => 'AssumeRole'
});
String stringToSign = this.createStringToSign(
'AWS4-HMAC-SHA256',
dt,
credentialScope,
this.createHashedCanonicalRequest(
'SHA256',
'POST',
'/',
params,
new Map<String, String>{
'Host' => 'sts.amazonaws.com',
'x-amz-date' => dt.formatGmt('YYYYMMdd') + 'T' + dt.formatGmt('HHmmss') + 'Z'
},
signedHeaders,
httpBody
)
);
String signature = this.createSignature(
'hmacSHA256',
dt,
REGION,
SERVICE,
Blob.valueOf(stringToSign)
);
return this.callCommonSTSCall(
dt,
'sts.amazonaws.com',
credentialScope,
signedHeaders,
signature,
'/',
httpBody
);
}
public String assumeRoleWithSAML(
String principalArn,
String roleArn,
String samlAssertion
) {
String httpBody = Utility.getParam(new Map<String, String>{
'PrincipalArn' => principalArn,
'RoleArn' => roleArn,
'Version' => '2011-06-15',
'Action' => 'AssumeRoleWithSAML',
'SAMLAssertion' => samlAssertion
});
HttpRequest req = new HttpRequest();
req.setEndpoint('https://sts.amazonaws.com');
req.setMethod('POST');
req.setBody(httpBody);
Http http = new Http();
HTTPResponse res = http.send(req);
return res.getBody();
}
public String createConsoleAccessURL(
String sessionId,
String sessionKey,
String sessionToken
) {
String jsonBody = EncodingUtil.urlEncode(JSON.serialize(new Map<String, Object>{
'sessionId' => sessionId,
'sessionKey' => sessionKey,
'sessionToken' => sessionToken
}), 'UTF-8');
HttpRequest req = new HttpRequest();
req.setEndpoint('https://signin.aws.amazon.com/federation?Action=getSigninToken&Session=' + jsonBody);
req.setMethod('GET');
Http http = new Http();
HTTPResponse res = http.send(req);
String response = res.getBody();
Map<String, Object> resMap = (Map<String, Object>)JSON.deserializeUntyped(response);
String returnUrl = 'https://signin.aws.amazon.com/federation' +
'?Action=login&' +
'Issuer=&' +
'Destination=https%3A%2F%2Fconsole.aws.amazon.com%2F&' +
'SigninToken=' + String.valueOf(resMap.get('SigninToken'));
return returnUrl;
}
/**
* STSへのAPIコールの共通メソッド
*/
private String callCommonSTSCall(
DateTime dt,
String hostname,
String credentialScope,
List<String> signedHeaders,
String signature,
String resource,
String httpBody
) {
HttpRequest req = new HttpRequest();
req.setHeader('Host', hostname);
req.setHeader(
'x-amz-date',
dt.formatGmt('YYYYMMdd') + 'T' + dt.formatGmt('HHmmss') + 'Z'
);
req.setHeader('Content-Length', String.valueOf(httpBody.length()));
req.setHeader(
'Authorization',
'AWS4-HMAC-SHA256 Credential=' + this.AWSAccessKeyId + '/' + credentialScope + ',' +
'SignedHeaders=' + String.join(Utility.getLowerCaseSortedList(signedHeaders), ';') + ',' +
'Signature=' + signature
);
req.setEndpoint('https://' + hostname + resource);
req.setMethod('POST');
req.setBody(httpBody);
Http http = new Http();
HTTPResponse res = http.send(req);
system.debug(res.getHeader('Location'));
return res.getBody();
}
/**
* HashedCanonicalRequestを作成
*/
private String createHashedCanonicalRequest(
String algorithm,
String method,
String url,
Map<String, String> params,
Map<String, String> headers,
List<String> signedHeaders,
String payload
) {
String signature = '';
signature += method + Constants.LF;
signature += url + Constants.LF;
signature += Utility.getSortedParam(params) + Constants.LF;
signature += Utility.createCanonicalHeaders(headers) + Constants.LF;
signature += String.join(Utility.getLowerCaseSortedList(signedHeaders), ';').toLowerCase() + Constants.LF;
signature += EncodingUtil.convertToHex(
Crypto.generateDigest(
algorithm,
Blob.valueOf(payload)
)
);
return EncodingUtil.convertToHex(
Crypto.generateDigest(
algorithm,
Blob.valueOf(signature)
)
).toLowerCase();
}
/**
* StringToSignを作成
*/
private String createStringToSign(
String algorithm,
DateTime requestDateTime,
String credentialScope,
String hashedCanonicalRequest
) {
String stringToSign = algorithm + Constants.LF;
stringToSign +=
requestDateTime.formatGmt('YYYYMMdd') + 'T' +
requestDateTime.formatGmt('HHmmss') + 'Z' + Constants.LF;
stringToSign += credentialScope + Constants.LF;
stringToSign += hashedCanonicalRequest;
return stringToSign;
}
private String createSignature(
String algorithm,
DateTime requestDateTime,
String region,
String service,
Blob stringToSign
) {
Blob kDate = Crypto.generateMac(
algorithm,
Blob.valueOf(requestDateTime.formatGmt('YYYYMMdd')),
Blob.valueOf('AWS4' + this.AWSAccessKeySecret)
);
Blob kRegion = Crypto.generateMac(
algorithm,
Blob.valueOf(region),
kDate
);
Blob kService = Crypto.generateMac(
algorithm,
Blob.valueOf(service),
kRegion
);
Blob kSigning = Crypto.generateMac(
algorithm,
Blob.valueOf('aws4_request'),
kService
);
return EncodingUtil.convertToHex(
Crypto.generateMac(
algorithm,
stringToSign,
kSigning
)
);
}
}
詳しいAPI仕様はこちらから。
STSは基本的にSignature Version4で、AssumeRoleWithSAMLだけは認証情報が必要ありません。つまり、単純なHTTP-POST or HTTP-GETでOKです。Console画面への自動ログインURLも1回HTTP-GETでSigninToken取ってきて、それをGETパラメータに含めてURL生成するだけで超簡単。
ただし、AssumeRoleWithSAMLをするときにはRoleの設定に注意してください。詳しくは過去の記事参照。