diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 4334720..df9e324 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,14 +1,14 @@ on: pull_request jobs: pre-commit: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - name: Checkout uses: actions/checkout@v1 - - name: Setup python 3.6 + - name: Setup python 3.8 uses: actions/setup-python@v1 with: - python-version: 3.6 + python-version: 3.8 - name: Install pre-commit run: pip install pre-commit - name: Run pre-commit @@ -36,10 +36,10 @@ jobs: # - name: Run license finder # run: license_finder test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 strategy: matrix: - python-version: ['2.7.x', '3.6.x', '3.7.x', '3.8.x'] + python-version: ['3.7.x', '3.8.x'] steps: - name: Checkout uses: actions/checkout@v1 diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 812f9f4..294a2cd 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -8,7 +8,7 @@ on: jobs: build-and-publish-python-module: name: Build and publish python module to pypi - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - name: Checkout uses: actions/checkout@v1 diff --git a/confidant_client/__init__.py b/confidant_client/__init__.py index 8b494d4..cf75838 100644 --- a/confidant_client/__init__.py +++ b/confidant_client/__init__.py @@ -444,6 +444,46 @@ def get_credential_services(self, id): ret['result'] = True return ret + def create_credential( + self, + name=None, + credential_pairs=None, + metadata=None, + enabled=None, + documentation=None + ): + """Create a credential and store it in Confidant.""" + # Return a dict, always with an attribute that specifies whether or not + # the function was able to successfully get a result. + ret = {'result': False} + if metadata is None: + metadata = {} + data = { + 'name': name, + 'credential_pairs': credential_pairs, + 'metadata': metadata, + 'enabled': enabled, + 'documentation': documentation + } + try: + response = self._execute_request( + 'post', + f'{self.config['url']}/v1/credentials', + headers=JSON_HEADERS, + data=json.dumps(data), + ) + except RequestExecutionError: + logging.exception('Error with executing request') + return ret + try: + data = response.json() + except ValueError: + logging.error('Received badly formatted json data from confidant.') + return ret + ret['credential'] = data + ret['result'] = True + return ret + def update_credential( self, id, diff --git a/confidant_client/cli.py b/confidant_client/cli.py index 4e68b4e..3ab691f 100644 --- a/confidant_client/cli.py +++ b/confidant_client/cli.py @@ -598,6 +598,90 @@ def _parse_args(): dest='resource_id', default=None, ) + + create_cred_parser = subparsers.add_parser( + 'create_credential', + help='Create a credential in confidant', + ) + create_cred_parser.add_argument( + '--name', + required=True, + help='The name of the credential', + ) + create_cred_parser.add_argument( + '--credential-pairs', + required=True, + help=('A dict of key/value pairs for credentials in json format i.e.' + '\'{"ssl_key":"----- BEGIN...","ssl_cert":"----- BEGIN..."}\'.'), + type=json.loads + ) + create_cred_parser.add_argument( + '--documentation', + required=True, + help='Documentation on how to rotate this credential' + ) + create_cred_parser.add_argument( + '--enabled', + help='Enable this credential (default).', + action='store_true', + dest='enabled' + ) + create_cred_parser.add_argument( + '--metadata', + help=('A dictionary of arbitrary key/value pairs for custom' + 'per-credential end-user extensions. This is not' + 'encrypted at rest.'), + type=json.loads, + ) + create_cred_parser.set_defaults( + enabled=True, + metadata={} + ) + + update_cred_parser = subparsers.add_parser( + 'update_credential', + help='Update a credential in confidant', + ) + update_cred_parser.add_argument( + '--id', + required=True, + help='The id of the credential to update', + dest='_id' + ) + update_cred_parser.add_argument( + '--name', + help='The name of the credential', + ) + update_cred_parser.add_argument( + '--credential-pairs', + required=True, + help=('A dict of key/value pairs for credentials in json format i.e.' + '\'{"ssl_key":"----- BEGIN...","ssl_cert":"----- BEGIN..."}\'.'), + type=json.loads + ) + update_cred_parser.add_argument( + '--documentation', + required=True, + help='Documentation on how to rotate this credential' + ) + update_cred_parser.add_argument( + '--enabled', + help='Enable this credential (default).', + action='store_true', + dest='enabled' + ) + update_cred_parser.add_argument( + '--metadata', + help=('A dictionary of arbitrary key/value pairs for custom' + 'per-credential end-user extensions. This is not' + 'encrypted at rest.'), + type=json.loads, + ) + update_cred_parser.set_defaults( + enabled=True, + metadata={} + ) + return parser.parse_args() @@ -798,6 +882,29 @@ def main(): ret = client.get_jwt(args.environment, args.resource_id) except Exception: logging.exception('An unexpected general error occurred.') + elif args.subcommand == 'create_credential': + try: + ret = client.create_credential( + args.name, + args.credential_pairs, + args.metadata, + args.enabled, + args.documentation + ) + except Exception: + logging.exception('An unexpected general error occurred.') + elif args.subcommand == 'update_credential': + try: + ret = client.update_credential( + args._id, + args.name, + args.credential_pairs, + args.metadata, + args.enabled, + args.documentation + ) + except Exception: + logging.exception('An unexpected general error occurred.') print(json.dumps(ret, sort_keys=True, indent=4, separators=(',', ': '))) if not ret['result']: diff --git a/setup.py b/setup.py index 5926470..e07146c 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup( name="confidant-client", - version="2.5.2", + version="2.5.3", packages=find_packages(exclude=["test*"]), install_requires=[ # Boto3 is the Amazon Web Services (AWS) Software Development Kit (SDK) diff --git a/tests/unit/confidant_client/client_test.py b/tests/unit/confidant_client/client_test.py index 5884b92..8c6454f 100644 --- a/tests/unit/confidant_client/client_test.py +++ b/tests/unit/confidant_client/client_test.py @@ -650,6 +650,42 @@ def test_get_credential_not_found(self): {'result': False} ) + @patch( + 'confidant_client.services.get_boto_client', + MagicMock() + ) + def test_create_credential(self): + client = confidant_client.ConfidantClient( + 'http://localhost/', + 'alias/authnz-testing', + {'from': 'confidant-unittest', + 'to': 'test', + 'user_type': 'service'}, + ) + client._get_token = MagicMock() + client.request_session.request = mock_500 + self.assertEqual( + client.create_credential( + 'test-credential', + {'key1': 'val1'}, + {}, + True, + 'Test Documentation' + ), + {'result': False} + ) + client.request_session.request = mock_200 + self.assertEqual( + client.create_credential( + 'test-credential', + {'key1': 'val1'}, + {}, + True, + 'Test Documentation' + ), + {'result': True, 'credential': {}} + ) + @patch( 'confidant_client.services.get_boto_client', MagicMock()