Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate user credendials before trying to bootstrap. #209

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 65 additions & 15 deletions bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,11 +469,17 @@ def http_error_206(self, request, response, code, msg, hdrs):
return response


def call_api(url, data=None, method='GET'):
def call_api(url, data=None, method='GET', no_verify_ssl=False, silent=False):
"""
Helper function to place an API call returning JSON results and doing
some error handling. Any error results in an exit.
"""
if sys.version_info >= (2, 7) and no_verify_ssl:
# import the ssl module only when on Python 2.7
import ssl
ssl_context = ssl._create_unverified_context()
else:
ssl_context = None
try:
request = urllib2.Request(url)
if options.verbose:
Expand All @@ -487,23 +493,27 @@ def call_api(url, data=None, method='GET'):
if data:
request.add_data(json.dumps(data))
request.get_method = lambda: method
result = urllib2.urlopen(request)
if ssl_context:
result = urllib2.urlopen(request, context=ssl_context)
else:
result = urllib2.urlopen(request)
jsonresult = json.load(result)
if options.verbose:
print 'result: %s' % json.dumps(jsonresult, sort_keys=False, indent=2)
return jsonresult
except urllib2.URLError, e:
print 'An error occured: %s' % e
print 'url: %s' % url
if isinstance(e, urllib2.HTTPError):
print 'code: %s' % e.code
if data:
print 'data: %s' % json.dumps(data, sort_keys=False, indent=2)
try:
jsonerr = json.load(e)
print 'error: %s' % json.dumps(jsonerr, sort_keys=False, indent=2)
except:
print 'error: %s' % e
if not silent:
print 'An error occured: %s' % e
print 'url: %s' % url
if isinstance(e, urllib2.HTTPError):
print 'code: %s' % e.code
if data:
print 'data: %s' % json.dumps(data, sort_keys=False, indent=2)
try:
jsonerr = json.load(e)
print 'error: %s' % json.dumps(jsonerr, sort_keys=False, indent=2)
except:
print 'error: %s' % e
sys.exit(1)
except Exception, e:
print "FATAL Error - %s" % (e)
Expand Down Expand Up @@ -769,6 +779,28 @@ class MEOptions:
disable_rhn_plugin()


def validate_login():
"""
Function to authenticate a user to validate login credentials before
running any other API calls. Since we don't have API_PORT setup yet
we need to make a test. Strip down output so it doesn't print a
big exception and scare users. This function is going to be used
before we have the certificates from katello-consumer rpm installed,
so we need to disable SSL verification since it's now enabled
per default in RHEL7.4+.
"""
if not API_PORT:
print "ERROR, ports 443 and 8443 seems closed, cannot connect."
sys.exit(2)

myurl = "https://" + options.foreman_fqdn + ":" + API_PORT + "/api/v2/organizations/"
try:
call_api(myurl, no_verify_ssl=True, silent=True)
except:
print "ERROR, could not authenticate username. Please try again."
sys.exit(2)


def enable_service(service, failonerror=True):
"""
Helper function to enable a service using proper init system.
Expand Down Expand Up @@ -804,6 +836,20 @@ def exec_service(service, command, failonerror=True):
exec_failok("/sbin/service %s %s" % (service, command))


def guess_api_port(host):
Copy link
Member

Choose a reason for hiding this comment

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

I think I'd go with the following version:

def guess_api_port(hostname):
    """Helper function to get the API port by probing"""
    for port in [443, 8443]:
        url = 'https://' + hostname + ':' + str(port) + '/katello/api/status'
        try:
            call_api(url, no_verify_ssl=True, silent=True, safe=True)
            return str(port)
        except:  # noqa: E722, pylint:disable=bare-except
            pass
    return "443"

the /katello/api/status endpoint doesn't require authentication and returns a JSON.

We just need to make call_api not to call sys.exit(1) if safe=True

"""
We need to guess the port to be used to validate login credentials
since we don't have API_PORT globally defined yet.
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
for port in [8443, 443]:
if sock.connect_ex((host, port)) == 0:
sock.close()
return str(port)
return None


if __name__ == '__main__':

print "Foreman Bootstrap Script"
Expand Down Expand Up @@ -833,8 +879,7 @@ def exec_service(service, command, failonerror=True):
if not MAC:
MAC = "00:00:00:00:00:00"

# > Gather API port (HTTPS), ARCHITECTURE and (OS) RELEASE
API_PORT = "443"
# > Gather ARCHITECTURE and (OS) RELEASE
ARCHITECTURE = get_architecture()
try:
RELEASE = platform.linux_distribution()[1]
Expand Down Expand Up @@ -913,6 +958,9 @@ def exec_service(service, command, failonerror=True):
parser.print_help()
sys.exit(1)

# > Guess API_PORT needs to be done after options.foreman_fqdn is set, but as early as possible.
API_PORT = guess_api_port(options.foreman_fqdn)

# > Gather FQDN, HOSTNAME and DOMAIN using options.fqdn
# > If socket.fqdn() returns an FQDN, derive HOSTNAME & DOMAIN using FQDN
# > else, HOSTNAME isn't an FQDN
Expand Down Expand Up @@ -1013,6 +1061,8 @@ def exec_service(service, command, failonerror=True):
# > Clean the environment from LD_... variables
clean_environment()

validate_login()
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a comment like # > Validate credentials, this will ease the generation of the flow as described in https://github.com/Katello/katello-client-bootstrap/blob/master/CONTRIBUTING.md#developer-and-contributor-notes


# > IF RHEL 5 and not removing, prepare the migration.
if not options.remove and int(RELEASE[0]) == 5:
prepare_rhel5_migration()
Expand Down