How to implement authorization
in your backend with AWS IAM
About me
- 14 years in industry
- DevOps Consultant at Provectus
stanislav@ivashchenko.family
Stanislav
Ivashchenko
DevOps Lead
Boost your career
in Provectus
Typical app these days: many players
https://commons.wikimedia.org/wiki/File:Pride_and_Prejudice_Character_Map.png
2 Questions for backend
● Service discovery
● Credentials
Login to your application
A new service starts up, where to get credentials?
1. Bake into ami or docker image or use a hardcode?
2. Provision with Chef/Puppet/Ansible/script over ssh?
3. Get from s3 bucket?
4. Parameter Store?
5. Vault?
6. Surprisingly many more ways, actually!
7. The questions is absolutely the same for any code you run: EC2,
Lambda, ECS
S3, Parameter Store, Vault
But how do you login there in the first place?
Ah, IAM instance profiles!
Inspired by how Vault authenticates users
https://www.hashicorp.com/resources/deep-dive-vault-aws-auth-backend
But why use Vault or Parameter Store, go
directly with IAM and STS
WhoAmI Request on STS
● This is a cornerstone of the entire idea
● Such request actually exists: sts:GetCallerIdentity
● Signed requests live for 15 min
● Discussed https://github.com/hashicorp/vault/issues/948
● Implemented in Vault https://github.com/hashicorp/vault/pull/1962
MiM used for good with STS
Example implementation
● API server - simple RoR api
● Client - python script
● https://github.com/sam50/ror_aws_iam_auth
● 2 instances, client has an EC2 instance profile with role
What Client is doing
1. Generates signed STS GetCallerIdentity request
2. Sends it to server http://<ip name>:3000/authenticate
3. Gets JWT Auth token
4. Uses that token to do things in API
def generate_sts_request(AppId):
session = botocore.session.get_session()
client = session.create_client('sts')
endpoint = client._endpoint
operation_model = client._service_model.operation_model('GetCallerIdentity')
request_dict = client._convert_to_request_dict({}, operation_model)
request_dict['headers']['X-APP-ID'] = AppId
request = endpoint.create_request(request_dict, operation_model)
return {
'iam_http_request_method': base64.b64encode(request.method),
'iam_request_url': base64.b64encode(request.url),
'iam_request_body': base64.b64encode(request.body),
'iam_request_headers': base64.b64encode(json.dumps(dict(request.headers))),
}
$ ./api_client.py <AppId> http://<server>/authenticate
Blah-blah-Debug
{"auth_token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1NTk5OTYyMjZ9.H9zjYGAIUwBZY
5Kb3KlF9eegTph9GmBBbLNrki1450U"}
curl -H "Authorization:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1NTk5OTYyMjZ9.H9zjYGAIUwBZY5Kb3KlF9eegT
ph9GmBBbLNrki1450U" http://<server>:3000/items
What server is doing
1. A simple API ($rails new api1 --api)
2. Uses a JWT (gem 'jwt')
3. Uses simple command(gem 'simple_command')
4. Receives login(signed sts:GetCallerIdentity) at /authenticate
5. Sends signed request to STS*
6. (if-ok) Looks for the user by the Role ARN
7. (if-ok) issues a JWT token to the client
def authenticate_iam
uri = URI.parse("https://sts.amazonaws.com/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = "Net::HTTP::#{@iam_http_request_method.capitalize}".constantize.new(uri.request_uri)
request.set_form_data(Rack::Utils.parse_nested_query(@iam_request_body))
headers = JSON.parse(@iam_request_headers)
headers.each do |header, value|
request[header]=value
end
if headers['X-APP-ID'] != 'APP1-live'
return false
end
response = http.request(request)
. . . . . . . . . . . .
. . . . . . . . . .
if response.code != '200' || response.body.empty?
return false
else
xml = Nokogiri::XML(response.body)
stsarn = xml.remove_namespaces!.xpath("GetCallerIdentityResponse/GetCallerIdentityResult/Arn").text
if stsarn.empty?
return false
else
return stsarn.gsub("arn:aws:sts","arn:aws:iam").gsub("assumed-role","role").gsub(//[A-z0-9-]*$/,"")
end
end
return false
end
git clone https://github.com/sam50/ror_aws_iam_auth
cd ror_aws_iam_auth
bundle install
rake db:migrate
rails c
>User.create!(name:"client1", iamarn: "<Your role ARN here
arn:aws:iam::xxxx:role/role-name")
rails s -b 0.0.0.0 3000
Signed Request (b64Decoded)
{"iam_request_body": "Action=GetCallerIdentity&Version=2011-06-15",
"iam_request_url": "https://sts.amazonaws.com/",
"iam_request_headers": "{"Content-Length": "X","X-Amz-Date": "X", "X-APP-ID": "AppId", "User-
Agent": "Botocore/1.12.139 Python/2.7.15rc1 Linux/4.15.0-1032-aws", "X-Amz-Security-Token":
"<Token>", "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", "Authorization":
"AWS4-HMAC-SHA256 Credential=<CredsID>, SignedHeaders=content-type;host;x-amz-date;x-amz-
security-token;x-app-id, Signature=<Signiture>"}", "iam_http_request_method": "POST"}
Q&A
Thank you!
We are hiring!

How to implement authorization in your backend with AWS IAM

  • 1.
    How to implementauthorization in your backend with AWS IAM
  • 2.
    About me - 14years in industry - DevOps Consultant at Provectus [email protected] Stanislav Ivashchenko DevOps Lead
  • 3.
  • 4.
    Typical app thesedays: many players https://commons.wikimedia.org/wiki/File:Pride_and_Prejudice_Character_Map.png
  • 5.
    2 Questions forbackend ● Service discovery ● Credentials
  • 6.
    Login to yourapplication
  • 7.
    A new servicestarts up, where to get credentials? 1. Bake into ami or docker image or use a hardcode? 2. Provision with Chef/Puppet/Ansible/script over ssh? 3. Get from s3 bucket? 4. Parameter Store? 5. Vault? 6. Surprisingly many more ways, actually! 7. The questions is absolutely the same for any code you run: EC2, Lambda, ECS
  • 8.
    S3, Parameter Store,Vault But how do you login there in the first place? Ah, IAM instance profiles!
  • 9.
    Inspired by howVault authenticates users https://www.hashicorp.com/resources/deep-dive-vault-aws-auth-backend
  • 10.
    But why useVault or Parameter Store, go directly with IAM and STS
  • 11.
    WhoAmI Request onSTS ● This is a cornerstone of the entire idea ● Such request actually exists: sts:GetCallerIdentity ● Signed requests live for 15 min ● Discussed https://github.com/hashicorp/vault/issues/948 ● Implemented in Vault https://github.com/hashicorp/vault/pull/1962
  • 12.
    MiM used forgood with STS
  • 13.
    Example implementation ● APIserver - simple RoR api ● Client - python script ● https://github.com/sam50/ror_aws_iam_auth ● 2 instances, client has an EC2 instance profile with role
  • 14.
    What Client isdoing 1. Generates signed STS GetCallerIdentity request 2. Sends it to server http://<ip name>:3000/authenticate 3. Gets JWT Auth token 4. Uses that token to do things in API
  • 15.
    def generate_sts_request(AppId): session =botocore.session.get_session() client = session.create_client('sts') endpoint = client._endpoint operation_model = client._service_model.operation_model('GetCallerIdentity') request_dict = client._convert_to_request_dict({}, operation_model) request_dict['headers']['X-APP-ID'] = AppId request = endpoint.create_request(request_dict, operation_model) return { 'iam_http_request_method': base64.b64encode(request.method), 'iam_request_url': base64.b64encode(request.url), 'iam_request_body': base64.b64encode(request.body), 'iam_request_headers': base64.b64encode(json.dumps(dict(request.headers))), }
  • 16.
    $ ./api_client.py <AppId>http://<server>/authenticate Blah-blah-Debug {"auth_token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1NTk5OTYyMjZ9.H9zjYGAIUwBZY 5Kb3KlF9eegTph9GmBBbLNrki1450U"} curl -H "Authorization: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1NTk5OTYyMjZ9.H9zjYGAIUwBZY5Kb3KlF9eegT ph9GmBBbLNrki1450U" http://<server>:3000/items
  • 17.
    What server isdoing 1. A simple API ($rails new api1 --api) 2. Uses a JWT (gem 'jwt') 3. Uses simple command(gem 'simple_command') 4. Receives login(signed sts:GetCallerIdentity) at /authenticate 5. Sends signed request to STS* 6. (if-ok) Looks for the user by the Role ARN 7. (if-ok) issues a JWT token to the client
  • 18.
    def authenticate_iam uri =URI.parse("https://sts.amazonaws.com/") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = "Net::HTTP::#{@iam_http_request_method.capitalize}".constantize.new(uri.request_uri) request.set_form_data(Rack::Utils.parse_nested_query(@iam_request_body)) headers = JSON.parse(@iam_request_headers) headers.each do |header, value| request[header]=value end if headers['X-APP-ID'] != 'APP1-live' return false end response = http.request(request) . . . . . . . . . . . .
  • 19.
    . . .. . . . . . . if response.code != '200' || response.body.empty? return false else xml = Nokogiri::XML(response.body) stsarn = xml.remove_namespaces!.xpath("GetCallerIdentityResponse/GetCallerIdentityResult/Arn").text if stsarn.empty? return false else return stsarn.gsub("arn:aws:sts","arn:aws:iam").gsub("assumed-role","role").gsub(//[A-z0-9-]*$/,"") end end return false end
  • 20.
    git clone https://github.com/sam50/ror_aws_iam_auth cdror_aws_iam_auth bundle install rake db:migrate rails c >User.create!(name:"client1", iamarn: "<Your role ARN here arn:aws:iam::xxxx:role/role-name") rails s -b 0.0.0.0 3000
  • 21.
    Signed Request (b64Decoded) {"iam_request_body":"Action=GetCallerIdentity&Version=2011-06-15", "iam_request_url": "https://sts.amazonaws.com/", "iam_request_headers": "{"Content-Length": "X","X-Amz-Date": "X", "X-APP-ID": "AppId", "User- Agent": "Botocore/1.12.139 Python/2.7.15rc1 Linux/4.15.0-1032-aws", "X-Amz-Security-Token": "<Token>", "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", "Authorization": "AWS4-HMAC-SHA256 Credential=<CredsID>, SignedHeaders=content-type;host;x-amz-date;x-amz- security-token;x-app-id, Signature=<Signiture>"}", "iam_http_request_method": "POST"}
  • 22.
  • 23.
  • 24.