Ruby: S3 access with AWS instance profile - ruby

I have a ec2 instance which has a profile attached. I can use awscli and it uploads to the bucket fine.
root#ocr-sa-test:/# aws s3 ls s3://company-ocr-east/
PRE 7_day_expiry/
root#ocr-sa-test:/# touch foo
root#ocr-sa-test:/# aws s3 cp foo s3://company-ocr-east/foo
upload: ./foo to s3://company-ocr-east/foo
root#ocr-sa-test:/# aws s3 rm s3://company-ocr-east/foo
delete: s3://company-ocr-east/foo
I can't get it to work with the aws-sdk in ruby though. I get access denied.
irb(main):001:0> require "aws-sdk"
=> true
irb(main):002:0>
irb(main):003:0> credentials = Aws::InstanceProfileCredentials.new
irb(main):004:1* client = Aws::S3::Client.new(
irb(main):005:1* region: "us-east-1",
irb(main):006:1* credentials: credentials,
irb(main):007:0> )
irb(main):008:0>
irb(main):009:0>
irb(main):010:0>
irb(main):011:1* begin
irb(main):012:2* client.put_object(
irb(main):013:2* key: 'hello.txt',
irb(main):014:2* body: 'Hello World!',
irb(main):015:2* bucket: 'company-ocr-east',
irb(main):016:2* content_type: 'text/plain'
irb(main):017:1* )
irb(main):018:1* rescue Exception => e
irb(main):019:1* puts "S3 Upload Error: #{e.class} : Message: #{e.message}"
irb(main):020:0> end
S3 Upload Error: Aws::S3::Errors::AccessDenied : Message: Access Denied

These commands aren't perfectly equivalent, so it'll be instructive to determine what exactly differs on the wire as a result. In particular, the SDK is being instructed to use a specific region and to obtain STS tokens from IMDS, whilst the CLI is left to work things out from either its own defaults or a profile config. Besides which, they don't behave exactly the same.
To find out what's actually happening, means re-running both with applicable debug flags, viz:
aws --debug s3 cp hello.txt s3://bucketname/hello.txt
and
credentials = Aws::InstanceProfileCredentials.new(http_debug_output: $stdout)
client = Aws::S3::Client.new(region: 'us-east-1', credentials: credentials, http_wire_trace: true)
client.put_object(key: 'hello.txt', body: 'Hello World!', bucket: 'bucketname', content_type: 'text/plain')
These will generate heaps of output but it's all relevant and, crucially, comparable once you look past the noise. The first thing to verify is that the CLI is definitely talking to IMDS (it'll have requests to http://169.254.169.254 that culminate with something like "found credentials from IAM Role". If not, then the instance isn't configured how you thought, and there'll be clues in the log to explain how it is getting credentials, e.g. unexpected profile file, or environment variables. You'll also want to check they are obtaining the same role.
The second thing to compare is the subsequent sequences of PUT they both attempt. At this point in the debugging, almost everything else is equal, so it's very likely you can adjust the settings of the Ruby SDK client to match whatever the CLI is succeeding with.
The third possibility is that of a system firewall, or some kind of process-level mandatory access controls, user permissions, cgroups/containers etc. However, debugging your OS kernel & configuration would be a deep, dark rabbit hole, and in any case you've said this is "an EC2 instance" so it is, presumably, a plain old EC2 instance. If in fact the Ruby commands above are running under a different user ID, or inside a container, then maybe there's your answer already, it could well be a networking issue due to user/container/security controls or similar OS-level configuration that needs fixing up.
Obligatory warning: if you choose to post any of the log data, be careful to overwrite any credentials! I don't believe these debug traces are particularly replayable, but you don't want to find out the hard way if I'm wrong.

The access denied error may be caused by the "very aggressive" default timeout in Aws::InstanceProfileCredentials.
Try initializing it with a longer timeout or additional retries:
credentials = Aws::InstanceProfileCredentials.new({
retries: 2, # Integer, default: 1
http_open_timeout: 2.5, # Float, default: 1
http_read_timeout: 2.5 # Float, default: 1
})
The docs did not make clear if the timeout options were given as seconds or another duration. 2.5 seemed conservative, given the default. Further tweaking may be needed.
The AWS docs for the v3 Ruby API discuss the aggressive timeout in the Aws::S3::Client docs and you can see options to configure Aws::InstanceProfileCredentials.

Related

Is there a way to access Rails 6 secrets.yml hash as dot notation object

I would like to access Rails.application.secrets as an object till its deeper length.
Example: ../config/secrets.yml
development:
secret_key_base: ""
my_data:
user_email: "abc#example.io"
external_service:
remote:
password: ""
local:
password: ""
Presently Remote password of an external service is fetched using:
Rails.application.secrets.my_data[:external_service][:remote][:password]
Instead I would like to access it as below:
Rails.application.secrets.my_data.external_service.remote.password
Is there a way that I can configure my application to behave in above format?
Note:
Only config/secrets.yml must be affected
Please specify the file path/name where the configurations must be changed/added
Also suggest if there is an alternate way(gem, etc)
You might be able to put this in an initializer and achieve what you're looking for:
Rails.application.secrets = JSON.parse(Rails.application.secrets.to_json, object_class: OpenStruct)

Can't create bucket using aws-sdk ruby gem. Aws::S3::Errors::SignatureDoesNotMatch

I have a new computer and I'm trying to set up my AWS CLI environment so that I can run a management console I've created.
This is the code I'm running:
def create_bucket(bucket_args)
AWS_S3 = Aws::S3::Client.new(signature_version: 'v4')
AWS_S3.create_bucket(bucket_args)
end
Which raises this error:
Aws::S3::Errors::SignatureDoesNotMatch - The request signature we calculated does not match the signature you provided. Check your key and signing method.:
This was working properly on my other computer, which I no longer have access to. I remember debugging this same error on the other computer, and I thought I had resolved it by adding signature_version = s3v4 to my ~/.aws/config file. But this fix is not working on my new computer, and I'm not sure why.
To give some more context: I am using aws-sdk (2.5.5) and these aws cli specs: aws-cli/1.11.2 Python/2.7.12 Linux/4.4.0-38-generic botocore/1.4.60
In this case the issue was that my aws credentials (in ~/.aws/credentials) - specifically my secret token - were invalid.
The original had a slash in it:
xx/xxxxxxxxxxxxxxxxxxxxxxxxxx
which I didn't notice at first, so when I double clicked the token to select the word, it didn't include the first three characters. I then pasted this into the terminal when running aws configure.
To fix this, I found the correct, original secret acceess token and set the correct value in ~/.aws/credentials.

Ruby Sinatra/Postgres - can't connect to heroku database with PG.connect?

I am trying to get a site set up on Heroku using Sinatra and PostgreSQL. It worked locally (connecting to local database), but after pushing it to Heroku and changing my PG.connect to reflect that, I get an Internal Server Error the moment a page tries to access the database.
require 'uri'
require 'pg'
uri = URI.parse(ENV['DATABASE_URL'])
def db(uri)
begin
connection = PG.connect(uri.hostname, uri.port, nil, nil, uri.path[1..-1], uri.user, uri.password)
yield(connection)
ensure
connection.close
end
end
I am pretty sure these are parsing correctly, because ENV['DATABASE_URL'] displays the full postgres://user:password#host:port/database information that I'm expecting, and if I do the same in IRB uri.hostname, ui.port, etc all return what's expected .
This is my first time trying to get a site working on Heroku, so I am not even sure how to troubleshoot this. (And I googled for about all of yesterday.)
Results for heroku pg:
=== DATABASE_URL
Plan: Hobby-dev
Status: Available
Connections: 0/20
PG Version: 9.4.2
Created: 2015-05-30 19:24 UTC
Data Size: 17.7 MB
Tables: 5
Rows: 9320/10000 (In compliance, close to row limit)
Fork/Follow: Unsupported
Rollback: Unsupported
And all the tables show up when when I do heroku pg:psql <database> from the cli.
Some answers I've seen said to add database.yml to my root app directory, so:
production:
adapter: 'postgresql'
database: '<database>'
host: ENV['DATABASE_URL']
username: '<username>'
There's probably something simple I'm missing, but I haven't seen a complete guide for Sinatra/PSQL on Heroku - nothing that goes specifically into setting up and connecting to your database. (Everything seems Rails-related.)
In your database.yml file you need to specify the correct host for the host entry. You are passing what is stored in DATABASE_URL (something like postgres://user:password#host:port/database) but it should just be the host.
You will also need to specify a port if it isn't the default for PostgreSQL.
Edit: should also point out if you plan to store the host (or anything else - you definitely should for username and password) in an environment variable you'll need to wrap it, e.g. <%= ENV['HOST'] %>, not just ENV['HOST'] (i.e. how you have in the database.yml excerpt above)

Chef Solo - Role Data in Ruby DSL or JSON?

I am playing with Roles with Chef Solo (11.4.4 and 11.6.0). A bit Confused.
For Chef Solo runs, should roles be written in Ruby or JSON?
As per the official docs: About Roles, Roles can be stored as domain-specific Ruby (DSL) files or JSON data.
NOTE: chef-client uses Ruby for Roles, when these files are uploaded to Chef Server, they are converted to JSON. Whenever chef-repo is refreshed, the contents of all domain-specific Ruby files are re-compiled to JSON and re-uploaded to the server.
My question is, if the requirement is to run Chef in solo mode without a server and roles are needed, should the roles be written in Ruby or JSON (we don't have a server to convert Ruby to JSON)?
My guess is the latter. Does anyone know the correct answer?
BTW: I've seen people mixing Ruby and JSON in role files...
What is the Ruby DSL equivalent for rbenv.rb below?
Example, run rbenv + ruby-build cookbooks to install rbenv on Ubuntu.
rbenv.json
{
"run_list": ["role[rbenv]"]
}
roles/rbenv.rb
name "rbenv"
description "rbenv + ruby-build"
run_list(
"recipe[rbenv]",
"recipe[ruby_build]"
)
override_attributes(
:rbenv => {
:git_repository => "https://github.com/sstephenson/rbenv.git"
},
:ruby_build => {
:git_repository => "https://github.com/sstephenson/ruby-build.git"
}
)
Chef Solo run chef-solo -c solo.rb -j rbenv.json -l debug works as expected. This is to achieve cloning via HTTPS because it easier behind the firewall.
However, using a Ruby DSL version of role rbenv.rb like below
name "rbenv"
description "rbenv + ruby-build"
run_list "recipe[rbenv]", "recipe[ruby_build]"
# default_attributes ":rbenv" => {":install_prefix" => "/opt"}
override_attributes ":rbenv" => {":git_repository" => "https://github.com/sstephenson/rbenv.git"}, ":ruby_build" => {":git_repository" => "https://github.com/sstephenson/ruby-build.git"}
It didn't seem to work because it still used the default attributes (clone via git URL instead of HTTPS).
I am new to Ruby so most likely I made some mistakes in the DSL code, please help;-)
* git[/opt/rbenv] action sync[2013-09-03T03:44:53+00:00] INFO: Processing git[/opt/rbenv] action sync (rbenv::default line 91)
[2013-09-03T03:44:53+00:00] DEBUG: git[/opt/rbenv] finding current git revision
[2013-09-03T03:44:53+00:00] DEBUG: git[/opt/rbenv] resolving remote reference
================================================================================
Error executing action `sync` on resource 'git[/opt/rbenv]'
================================================================================
Mixlib::ShellOut::ShellCommandFailed
------------------------------------
Expected process to exit with [0], but received '128'
---- Begin output of git ls-remote "git://github.com/sstephenson/rbenv.git" master* ----
STDOUT:
STDERR: fatal: unable to connect to github.com:
github.com[0: 192.30.252.128]: errno=Connection timed out
---- End output of git ls-remote "git://github.com/sstephenson/rbenv.git" master* ----
Ran git ls-remote "git://github.com/sstephenson/rbenv.git" master* returned 128
I prefer to use JSON format wherever possible for one simple reason - it's easy to parse and validate with a script. Here are three things that you can do if all your Chef data is in JSON format:
Easily perform a syntax check in a git pre-commit hook, something that's much harder to do when the file is in the Ruby DSL format.
Validate the keys and values in a data bag entry. This can be useful to check that you are not going to deploy invalid or nonsensical data bag entries to production.
Compare (with a little extra work - key ordering in a dictionary needs to be taken into account) the value of an object on a server with what's in git. The --format json argument is useful here.

Deploying an app using the sqlite3 database through django WinXP/7

Current System: Windows XP / Windows 7 (problem occuring for both)
After following the guidelines for deployment from the following:
https://devcenter.heroku.com/articles/python
and by testing by using a simple poll application I am successfully able to push the application through heorku except that after checking the logs the following error appears:
2012-04-27T08:14:42+00:00 app[web.1]: django.core.exceptions.ImproperlyConfigure
d: Error loading either pysqlite2 or sqlite3 modules (tried in that order): No m
odule named _sqlite3
This also occurs when attempting to sync the database.
Here is the current configuration of the database in the settings.py file:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'database.sqlite', # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}
I am aware is it a sqlite3 database, and I have been told that it should still allow heroku to deploy the app without any errors.
I have followed through using the following potential solutions that are related to this problem:
No module named _sqlite3
How do I set up SQLite with a Django project?
http://groups.google.com/group/django-users/browse_thread/thread/185f981f432346f1
Any help will be appreciated! Please let me know if additional Information is needed.
Heroku does not support sqlite, since it only provides a read-only filesystem.
I had the same error with the settings file. Looking through the Heroku logs, it turned out that my settings.py file was failing for various reasons. Once I fixed those issues, Django stopped complaining about missing database settings.
One of the things that caused this issue was a monkey patch I was using to allow sub-selects as tables in QuerySet extra(). This patch is at the end of my settings file.
# Override default behaviour of compiler to quote table names when table name is a sub-query
from django.db.models.sql.compiler import SQLCompiler
_quote_name_unless_alias = SQLCompiler.quote_name_unless_alias
SQLCompiler.quote_name_unless_alias = lambda self,name: name if name.startswith('(') else _quote_name_unless_alias(self,name)
This patch apparently requires that DATABASES already be correctly specified at that point. Since Heroku appends the magic DATABASES configuration to the end of the settings file (i.e. after the monkey patch), I had to manually insert their configuration above my monkey patch.

Resources