Skip to content

Commit

Permalink
Merge pull request #5 from nathan-v/Feature_config_files
Browse files Browse the repository at this point in the history
Config file support - v0.3.0
  • Loading branch information
nathan-v authored Mar 29, 2018
2 parents ac0766b + 97556eb commit a41902f
Show file tree
Hide file tree
Showing 10 changed files with 685 additions and 106 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[report]
omit = */test/*
show_missing = True

exclude_lines =
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,40 @@ your Login Session to be - often a full work day.

See the `--reup` commandline option for help here!

## Config file .. predefined settings for you or your org

The config file, which defaults to `~/.config/aws_okta_keyman.yml`, allows you to
pre-set things like your username, Okta organization name (subdomain), and AWS accounts and App IDs to make this script simpler to use. This also supports username assumption
based on the current user when the username or email is configured as
`automatic-username` if usernames only are an option or
`[email protected]` if you need full emails. Arguments will always
be preferred to the config file so you can override what's in the config file
as needed on each run of the tool.

Example config file:

username: [email protected]
org: example
accounts:
- name: Test
appid: exampleAppIDFromOkta/123
- name: Dev
appid: exampleAppIDFromOkta/234
- name: Prod
appid: exampleAppIDFromOkta/345

When used you'll get a similar interface to AWS Role selection but for your AWS
accounts:

$ aws_okta_keyman
16:56:47 (INFO) AWS Okta Keyman v0.3.0
16:56:47 (WARNING) No app ID provided; please select from available AWS accounts
[0] Account: Test
[1] Account: Dev
[2] Account: Prod
Select an account from above: 0
16:56:49 (INFO) Using account: Test / exampleAppIDFromOkta/123

# Usage

For detailed usage instructions, see the `--help` commandline argument.
Expand Down
2 changes: 2 additions & 0 deletions aws_okta_keyman/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,5 +213,7 @@ def _write(self):
name=self.profile,
region=self.region,
creds=self.creds)
log.info('Current time is {time}'.format(
time=datetime.datetime.utcnow()))
log.info('Session expires at {time}'.format(
time=self.creds['Expiration']))
216 changes: 216 additions & 0 deletions aws_okta_keyman/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright 2018 Nextdoor.com, Inc
# Copyright 2018 Nathan V

import argparse
import getpass
import logging
import os

import yaml
from aws_okta_keyman.metadata import __version__

log = logging.getLogger(__name__)


class Config:
def __init__(self, argv):
self.argv = argv
self.config = None
self.writepath = None
self.org = None
self.accounts = None
self.username = None
self.reup = None
self.debug = None
self.appid = None
self.name = 'default'

def set_appid_from_account_id(self, account_id):
self.appid = self.accounts[account_id]['appid']

def validate(self):
if self.appid is None:
if not self.accounts:
raise ValueError('The appid parameter is required if accounts '
'have not been set in the config file.')
required = ['org', 'username']
for arg in required:
if getattr(self, arg) is None:
err = ("The parameter {} must be provided in the config file "
"or as an argument".format(arg))
raise ValueError(err)

self.username = self.username.replace('automatic-username',
getpass.getuser())

def get_config(self):
file = os.path.expanduser('~') + '/.config/aws_okta_keyman.yml'
if '-w' in self.argv[1:] or '--writepath' in self.argv[1:]:
self.parse_args(main_required=False)
self.write_config()
elif '-c' in self.argv[1:] or '--config' in self.argv[1:]:
self.parse_args(main_required=False)
self.parse_config(self.config)
elif os.path.isfile(file):
# If we haven't been told to write out the args and no filename is
# given just use the default path
self.parse_args(main_required=False)
self.parse_config(file)
else:
# No default file, none specified; operate on args only
self.parse_args()
self.validate()

def usage_epilog(self):
epilog = (
'** Application ID **\n'
'The ApplicationID is actually a two part piece of the redirect\n'
'URL that Okta uses when you are logged into the Web UI. If you\n'
'mouse over the appropriate Application and see a URL that looks\n'
' like this. \n'
'\n'
'\thttps://foobar.okta.com/home/amazon_aws/0oaciCSo1d8/123?...\n'
'\n'
'You would enter in "0oaciCSo1d8/123" as your Application ID.\n'
'\n'
'** Configuration File **\n'
'AWS Okta Keyman can use a config file to pre-configure most of\n'
'the settings needed for execution. The default location is \n'
'\'~/.config/aws_okta_keyman.yml\' on Linux/Mac or for Windows \n'
'it is \'$USERPROFILE\\.config\\aws_okta_keyman.yml\'\n')
return epilog

def parse_args(self, main_required=True):
'''Returns a configured ArgumentParser for the CLI options'''
arg_parser = argparse.ArgumentParser(
prog=self.argv[0],
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=self.usage_epilog(),
description="AWS Okta Keyman v{}".format(__version__))
# Remove the default optional arguments section that always shows up.
# It's not necessary, and can cause confusion.
# https://stackoverflow.com/questions/24180527/
# argparse-required-arguments-listed-under-optional-arguments
arg_parser._action_groups.pop()

optional_args = arg_parser.add_argument_group('Optional arguments')

if main_required:
required_args = arg_parser.add_argument_group('Required arguments '
'or settings')
self.main_args(required_args, main_required)
else:
self.main_args(optional_args)

self.optional_args(optional_args)

config = arg_parser.parse_args(args=self.argv[1:])
config_dict = vars(config)

for key in config_dict:
setattr(self, key, config_dict[key])

@staticmethod
def main_args(arg_group, required=False):
arg_group.add_argument('-o', '--org', type=str,
help=(
'Okta Organization Name - ie, if your '
'login URL is https://foobar.okta.com, '
'enter in foobar here'
),
required=required)
arg_group.add_argument('-u', '--username', type=str,
help=(
'Okta Login Name - either '
'[email protected], or just bob works too,'
' depending on your organization '
'settings.'
),
required=required)
arg_group.add_argument('-a', '--appid', type=str,
help=(
'The "redirect link" Application ID - '
'this can be found by mousing over the '
'application in Okta\'s Web UI. See '
'details below for more help.'
),
required=required)

@staticmethod
def optional_args(optional_args):
optional_args.add_argument('-V', '--version', action='version',
version=__version__)
optional_args.add_argument('-D', '--debug', action='store_true',
help=(
'Enable DEBUG logging - note, this is '
'extremely verbose and exposes '
'credentials so be careful here!'
),
default=False)
optional_args.add_argument('-r', '--reup', action='store_true',
help=(
'Automatically re-up the AWS creds '
'before they expire.'
), default=0)
optional_args.add_argument('-n', '--name', type=str,
help='AWS Profile Name', default='default')
optional_args.add_argument('-c', '--config', type=str,
help='Config File path')
optional_args.add_argument('-w', '--writepath', type=str,
help='Full config file path to write to',
default='~/.config/aws_okta_keyman.yml')

def parse_config(self, filename):
if os.path.isfile(filename):
config = yaml.load(open(filename, 'r'))
else:
raise IOError("File not found: {}".format(filename))

log.debug("YAML loaded config: {}".format(config))

for key, value in config.items():
if getattr(self, key) is None: # Only overwrite None not args
setattr(self, key, value)

def write_config(self):
file_path = os.path.expanduser(self.writepath)
if os.path.isfile(file_path):
config = yaml.load(open(file_path, 'r'))
else:
config = {}

log.debug("YAML loaded config: {}".format(config))

args_dict = dict(vars(self))

# Combine file data and user args with user args overwriting
for key, value in config.items():
setattr(self, key, value)
for key in args_dict:
if args_dict[key] is not None:
setattr(self, key, args_dict[key])

config = dict(vars(self))
# Remove args we don't want to save to a config file
for var in ['name', 'appid', 'argv', 'writepath', 'config', 'debug']:
del config[var]

if config['accounts'] is None:
del config['accounts']

log.debug("YAML being saved: {}".format(config))

with open(file_path, 'w') as outfile:
yaml.safe_dump(config, outfile, default_flow_style=False)
Loading

0 comments on commit a41902f

Please sign in to comment.