Class: SimplyGenius::Atmos::Providers::Aws::AuthManager
- Inherits:
-
Object
- Object
- SimplyGenius::Atmos::Providers::Aws::AuthManager
- Includes:
- FileUtils, GemLogger::LoggerSupport, UI
- Defined in:
- lib/simplygenius/atmos/providers/aws/auth_manager.rb
Instance Method Summary collapse
- #authenticate(system_env, **opts, &block) ⇒ Object
-
#initialize(provider) ⇒ AuthManager
constructor
A new instance of AuthManager.
Methods included from UI
#agree, #ask, #choose, color_enabled, color_enabled=, #display, #error, #notify, #say, #warn
Constructor Details
#initialize(provider) ⇒ AuthManager
Returns a new instance of AuthManager.
19 20 21 |
# File 'lib/simplygenius/atmos/providers/aws/auth_manager.rb', line 19 def initialize(provider) @provider = provider end |
Instance Method Details
#authenticate(system_env, **opts, &block) ⇒ Object
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/simplygenius/atmos/providers/aws/auth_manager.rb', line 23 def authenticate(system_env, **opts, &block) profile = system_env['AWS_PROFILE'] key = system_env['AWS_ACCESS_KEY_ID'] secret = system_env['AWS_SECRET_ACCESS_KEY'] if profile.blank? && (key.blank? || secret.blank?) logger.warn("An aws profile or key/secret should be supplied via the environment") end # Handle bootstrapping a new env account. Newly created organization # accounts only have the default role that can only be assumed by an # iam user, so use that as the target for assume_role, and the root # check below will ensure iam user assume_role_name = nil if opts[:bootstrap] && Atmos.config.atmos_env != 'ops' # TODO: do this hack better assume_role_name = Atmos.config["auth.bootstrap_assume_role_name"] else assume_role_name = opts[:role] || Atmos.config["auth.assume_role_name"] end account_id = Atmos.config.account_hash[Atmos.config.atmos_env].to_s role_arn = "arn:aws:iam::#{account_id}:role/#{assume_role_name}" user_name = nil begin sts = ::Aws::STS::Client.new resp = sts.get_caller_identity arn_pieces = resp.arn.split(":") user_name = arn_pieces.last.split("/").last # root credentials can't assume role, but they should have full # access for the current account, so proceed (e.g. for bootstrap). if arn_pieces.last == "root" # We check the account of the caller to prevent root user of ops # account from bootstrapping an env account, but still allow a # root user of the env account itself to be able to bootstrap # (i.e. to allow not organizational accounts to bootstrap using # their root user) if arn_pieces[-2] != account_id logger.error <<~EOF Account doesn't match credentials. Bootstrapping a new account should be done as an iam user from the ops account or using credentials for a root user of the env account. EOF exit(1) end # Should only use root credentials for bootstrap, and thus we # won't have role requirement for mfa, etc, even if root account # uses mfa for login. Thus skip all the other stuff, to # encourage/force use of non-root accounts for normal use logger.warn("Using aws root credentials - should only be neccessary for bootstrap") return block.call(Hash[system_env]) end rescue ::Aws::STS::Errors::ServiceError => e logger.error "Could not discover aws credentials" exit(1) end auth_needed = true cache_key = "#{user_name}-#{assume_role_name}" credentials = read_auth_cache[cache_key] if credentials.present? logger.debug("Session cache present, checking expiration...") expiration = Time.parse(credentials['expiration']) session_renew_interval = (session_duration / 4).to_i if Time.now > expiration logger.debug "Session cache is expired, performing normal auth" auth_needed = true elsif Time.now > (expiration - session_renew_interval) begin # TODO: investigate making all info a warn so we don't pollute stdout for shell scripts logger.info "Session approaching expiration, renewing..." credentials = assume_role(role_arn, credentials: credentials, user_name: user_name) write_auth_cache(cache_key => credentials) auth_needed = false rescue => e logger.info "Failed to renew credentials using session cache, reason: #{e.}" auth_needed = true end else logger.debug "Session cache is current, skipping auth" auth_needed = false end end if auth_needed begin logger.info "No active session cache, authenticating..." credentials = assume_role(role_arn, user_name: user_name) write_auth_cache(cache_key => credentials) rescue ::Aws::STS::Errors::AccessDenied => e if e. !~ /explicit deny/ logger.debug "Access Denied, reason: #{e.}" end logger.info "Normal auth failed, checking for mfa" iam = ::Aws::IAM::Client.new response = iam.list_mfa_devices(user_name: user_name) mfa_serial = response.mfa_devices.first.try(:serial_number) token = nil if mfa_serial.present? token = Otp.instance.generate(user_name) if token.nil? token = ask("Enter token to retry with mfa: ") else logger.info "Used integrated atmos mfa to generate token" end if token.blank? logger.error "A MFA token must be supplied" exit(1) end else logger.error "MFA is not setup for your account, retry after doing so" exit(1) end credentials = assume_role(role_arn, serial_number: mfa_serial, token_code: token, user_name: user_name) write_auth_cache(cache_key => credentials) end end process_env = {} process_env['AWS_ACCESS_KEY_ID'] = credentials['access_key_id'] process_env['AWS_SECRET_ACCESS_KEY'] = credentials['secret_access_key'] process_env['AWS_SESSION_TOKEN'] = credentials['session_token'] logger.debug("Calling authentication target with env: #{process_env.inspect}") block.call(Hash[system_env].merge(process_env)) end |