Skip to content
This repository has been archived by the owner on Sep 9, 2024. It is now read-only.

Create hostfactory token vanilla flow 12446 #339

Merged
merged 15 commits into from
Sep 30, 2021

Conversation

eranha
Copy link
Contributor

@eranha eranha commented Sep 29, 2021

What does this PR do?

  • What's changed? Why were these changes made?
    Create token - vanilla flow
    Added menu parser controller logic and data classes
    Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
    A token can be created after adding a hostfactory policy

What ticket does this PR close?

12446

Checklists

Change log

  • The CHANGELOG has been updated, or
  • This PR does not include user-facing changes and doesn't require a CHANGELOG update

Test coverage

  • This PR includes new unit and integration tests to go with the code changes, or
  • The changes in this PR do not require tests

Documentation

  • Docs (e.g. READMEs) were updated in this PR, and/or there is a follow-on issue to update docs, or
  • This PR does not require updating any documentation

@eranha eranha requested a review from sigalsax September 29, 2021 09:28
@eranha eranha requested review from a team as code owners September 29, 2021 09:28
'10.0.11.1/32,10.0.20.0/24")')
hostfactory_create_token.add_argument('-d', '--duration-days', metavar='VALUE', type=int,
help='(Optional) the number of days the token will be valid.')
hostfactory_create_token.add_argument('-H', '--duration-hours', metavar='VALUE', type=int,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please consult with Sapir on this

@@ -180,6 +181,12 @@ def get_many(self, *variable_ids) -> Optional[bytes]:
"""
return self._api.get_variables(*variable_ids)

def create_token(self, create_token_data: CreateTokenData) -> str:
"""
Create token/s for hosts with restrictions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we create only a single token, correct?

Suggested change
Create token/s for hosts with restrictions
Create token for hosts with restrictions

Copy link
Contributor Author

@eranha eranha Sep 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You pass an argument count and get the multiple tokens in response

@@ -13,7 +13,11 @@
from datetime import datetime, timedelta

# Internals
from urllib import parse
import requests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be under # Third Parties

Copy link
Contributor

@mbenita-Cyberark mbenita-Cyberark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very good work

@@ -221,6 +225,28 @@ def get_variables(self, *variable_ids) -> dict:

return remapped_keys_dict

def create_token(self, create_token_data: CreateTokenData) -> requests.Response:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you return response here but in the client.py calling this function you say you are returning a str. is it correct?

This method is used to create token/s for hosts with restrictions.
"""
if create_token_data is None:
raise MissingRequiredParameterException('create_token_data')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency I suggest show an error message for MissingRequiredParameterException like in other places for example in hte api constructor "Account cannot be empty!"

@@ -180,6 +181,12 @@ def get_many(self, *variable_ids) -> Optional[bytes]:
"""
return self._api.get_variables(*variable_ids)

def create_token(self, create_token_data: CreateTokenData) -> str:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually return response no?

Method that facilitates create token call to the logic
"""
if create_token_data is None:
raise MissingRequiredParameterException('create_token_data')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency maybe something like Token data is missing? this is for the UI. Even though I have to say that your way is much easier to debug. @sigalsax maybe we should use this approach for all our MissingRequiredParameterException ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbenita-Cyberark agreed. This error isn't enough to explain what happened. It will just print out 'create_token_data'. Please a more explanatory error of what happened at tag Shuli

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sigalsax his should not happen to user unless a developer did something wrong.

Used for organizing the params the user passed in to execute the CreateToken command
"""

def __init__(self, **arg_params):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why kwargs and not just simple parameters? is the input dynamically changes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should always be as explicit as possible and avoid kwargs

if days == 0 and hours == 0 and minutes == 0:
return default_expiration

return datetime.now() + timedelta(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the datetime.now allign to the server? for our testing env working with aws n.virgia it's not. should we work with utc?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eladkug I think this is a very shaky point that could cause future bugs due to server changes. can you suggest a test for this?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both working with UTC so its o.k. (I verified with @eranha )

params.update(self._default_params)

token_params = {}
for attr, value in create_token_data.__dict__.items():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we use repr function only here maybe we could set it to return only fields that have values?

Comment on lines 29 to 30
if create_token_data is None:
raise MissingRequiredParameterException('create_token_data')
Copy link
Contributor

@mbenita-Cyberark mbenita-Cyberark Sep 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is being validate in three different places.. with only two possible paths (sdk going to the api, and cli going controller-> logic -> api) maybe we can skip this validation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the case now it can change I prefer not to make this assumption


response = self.client.create_token(create_token_data)

if response is not None and response.json() is not None and len(response.json()) > 0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are deserializing the content three times with response.json()
maybe

Suggested change
if response is not None and response.json() is not None and len(response.json()) > 0:
if response is not None :
data = response.json()
if data is not None and len(data) > 0 :
return json.dumps(data, indent=4, sort_keys=True)

# pylint: disable=too-many-locals,consider-using-f-string
# ssl_verify can accept Boolean or String as per requests docs
# https://requests.readthedocs.io/en/master/api/#main-interface
def invoke_endpoint(http_verb: HttpVerb, endpoint: ConjurEndpoint, params: dict, *args,
check_errors: bool = True, ssl_verify: bool = True,
auth: tuple = None, api_token: str = None,
query: dict = None) -> requests.Response:
query: dict = None, headers: dict = {}) -> requests.Response:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very dangerous and possibly the source of all evil headers: dict = {} pycharm should warn you about this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great point @mbenita-Cyberark

Comment on lines 102 to 114
hostfactory_create_token.add_argument('--cidr', metavar='VALUE',
help='(Optional) the CIDR address that contains all IPs that can '
'use this token to create hosts. Yoo can specify multiple cidr, '
'separated by commas (for example --cidr "10.0.10.0/24,'
'10.0.11.1/32,10.0.20.0/24")')
hostfactory_create_token.add_argument('-d', '--duration-days', metavar='VALUE', type=int,
help='(Optional) the number of days the token will be valid.')
hostfactory_create_token.add_argument('-H', '--duration-hours', metavar='VALUE', type=int,
help='(Optional) the number of hours the token will be valid.')
hostfactory_create_token.add_argument('-m', '--duration-minutes', metavar='VALUE', type=int,
help='(Optional) the number of minutes the token will be valid.')
hostfactory_create_token.add_argument('-c', '--count', metavar='VALUE', type=int,
help='(Optional) the number of times the token can be used.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this PR for vanilla flow only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean no args?

conjur/cli.py Outdated
@classmethod
def handle_hostfactory_logic(cls, args:list=None, client=None):
"""
Method that wraps the hostfdactory call logic
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Method that wraps the hostfdactory call logic
Method that wraps the hostfactory call logic

parse.urlencode(token_params),
api_token=self.api_token,
ssl_verify=self._ssl_verify,
headers={'Content-Type': 'application/x-www-form-urlencoded'})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we return a JSON or a string instead of the response itself? Would be clearer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation of the logic class
`HostFactoryLogic

This class holds the business logic for executing and manipulating
returned data`

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont follow

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sigalsax The logic responsible to manipulate the response


hostfactory_create_subcommand_parser = menu \
.add_parser(name="token",
help=' Create token/s for hosts with restrictions',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
help=' Create token/s for hosts with restrictions',
help='Create token/s for hosts with restrictions',

# Options
hostfactory_create_token = hostfactory_create_subcommand_parser.add_argument_group(
title=title_formatter("Options"))
hostfactory_create_token.add_argument('-action_type', default='create_token', help=argparse.SUPPRESS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It helps diff create from revoke they both end in cli.handle_hostfactory_logic

if args.action_type == 'create_token':
hostfactory_logic = HostFactoryLogic(client)
create_token_data = CreateTokenData(hostfactoryid=args.hostfactoryid,
cidr=args.cidr,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would make attributes in CreateTokenData more readible maybe id, cidr, days, hours, minutes, count

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omit duration? is token days more clear than duration days?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the attribute names are just very long and we should make them dev-friendly :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sigalsax I agree

conjur/cli.py Outdated
@@ -290,6 +310,9 @@ def run_action(resource:str, args):
result = client.whoami()
print(json.dumps(result, indent=4))

elif resource == 'hostfactory':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks better if we put it under 'host' logic. it becomes easier to spot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switch the order of the elifs. Move this block under the host elif block for readability

from conjur.errors import MissingRequiredParameterException

"""
VariableController module
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
VariableController module
HostfactoryController module

self.count = arg_params['count'] if arg_params['count'] else 1

self.expiration = self.get_expiration(
arg_params['duration_days'] if arg_params['duration_days'] else 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please get Sapir's approval on this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on count?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on the default

# -*- coding: utf-8 -*-

"""
VariableLogic module
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
VariableLogic module
HostfactoryLogic module

"""

# Internals
import json
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json isn't an Internals it is Builtins

@sigalsax sigalsax self-requested a review September 30, 2021 05:39
Copy link
Contributor

@sigalsax sigalsax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! @eranha
Left a few comments and please see the failing build. We are failing on the code linter suggestions so please address them :)

hostfactory_create_token = hostfactory_create_subcommand_parser.add_argument_group(
title=title_formatter("Options"))
hostfactory_create_token.add_argument('-action_type', default='create_token', help=argparse.SUPPRESS)
hostfactory_create_token.add_argument('-i', '--hostfactoryid', metavar='VALUE',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eranha i think this sould be --hostfactory-id

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sigalsax see my comment below

description=command_description(hostfactory_name,
hostfactory_usage),
epilog=command_epilog(
'conjur hostfactory create token --hostfactoryid my_factory --cidr 10.10.1.2/31 '
Copy link
Contributor

@sigalsax sigalsax Sep 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think it should be --hostfactory-id

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sigalsax this is how it appears in the sign-off doc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consult Sapir with this because the rest of the flags have - (duration-days, etc)

This method is used to create token/s for hosts with restrictions.
"""
if create_token_data is None:
raise MissingRequiredParameterException('create_token_data cannot be empty!')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eranha is this a user facing log? If so, this doesn't really explain anything

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sigalsax I consider it as an argument exception (runtime so to speak) it is intended for the developer

@eranha eranha requested a review from sigalsax September 30, 2021 12:10
description=command_description(hostfactory_name,
hostfactory_usage),
epilog=command_epilog(
'conjur hostfactory create token --hostfactoryid my_factory '
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a blocker but this should be verified by Sapir (if it needs a -)

'10.0.11.1/32,10.0.20.0/24")')
create_token.add_argument('-d', '--duration-days', metavar='VALUE', type=int,
help='(Optional) the number of days the token will be valid.')
create_token.add_argument('-H', '--duration-hours', metavar='VALUE', type=int,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was anything decided here regarding the -H?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sigalsax not that I know of

@staticmethod
def get_expiration(duration: timedelta) -> datetime:
"""
Returns the token expiration in UTC; One hour of not specified.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Returns the token expiration in UTC; One hour of not specified.
Returns the token expiration in UTC; One hour if not specified.

doseq=True))
if response is not None:
data = response.json()
if data is not None and len(data) > 0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need a compound check?
if Data is not none then its length automatically will be greater > 0 no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessarily

Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
@eranha eranha force-pushed the create_token_vanilla_flow_12446 branch from e8d48d2 to 12c4563 Compare September 30, 2021 12:40
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
@eranha eranha requested a review from sigalsax September 30, 2021 13:39
conjur/cli.py Outdated
days = args.duration_days if args.duration_days else 0
hours = args.duration_hours if args.duration_hours else 0
minutes = args.duration_minutes if args.duration_minutes else 0
duration = timedelta(days=days, hours=hours, minutes=minutes)
default_duration = timedelta(hours=1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought to put this in the controller actually @eranha

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is the view

Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
@eranha eranha requested a review from sigalsax September 30, 2021 14:24
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Basic skeleton was created to allow easy entry into task (Addition was made to help screen, appropriate classes were added)
Copy link
Contributor

@sigalsax sigalsax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work!

@eranha eranha merged commit 56d92ff into main Sep 30, 2021
@eranha eranha deleted the create_token_vanilla_flow_12446 branch September 30, 2021 15:21
@sigalsax sigalsax changed the title Create token vanilla flow 12446 Create hostfactory token vanilla flow 12446 Oct 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants