Skip to content

Commit

Permalink
CVE-2017-15112 unsafe use of -p/--admin-password on command line
Browse files Browse the repository at this point in the history
It is unsafe to pass a password as part of the command line args
because it appears in the process info and is recorded in the
shell command history.

The -p --keycloak-admin-password arg has been replaced with -P
--keycloak-admin-password-file which reads the password from a file or
stdin if given a hyphen. The now deprecated --keycloak-admin-password
also accepted stdin with a hyphen, this was retained for backward
compatibility during a transition period, it will issue a deprecation
warning. Trying to use --keycloak-admin-password with anything other
than a hyphen will result in an error and explanation.

The man page was updated to include a section on password passing and
includes documention on using the KEYCLOAK_ADMIN_PASSWORD environment
variable (which was always supported but never documented).

The --admin-password command line arg in keycloak_cli.py was also
updated in a similar manner and now also includes support for
KEYCLOAK_ADMIN_PASSWORD environment variable.
  • Loading branch information
John Dennis committed Jan 9, 2018
1 parent bee4ab8 commit c3121b2
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 12 deletions.
63 changes: 57 additions & 6 deletions bin/keycloak-httpd-client-install
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,29 @@ def arg_type_mellon_protected_location(value):
'(arg="%s")' % value)
return value.rstrip(" /")

class DeprecatedPasswordAction(argparse.Action):
def __init__(self, option_strings, dest, nargs=None, **kwargs):
super(DeprecatedPasswordAction, self).__init__(option_strings, dest,
**kwargs)

def __call__(self, parser, namespace, values, option_string=None):
msg1 = ('%s Deprecated. Use --keycloak-admin-password-file instead '
'(which also accepts - to read from stdin) '
'or the KEYCLOAK_ADMIN_PASSWORD environment variable. '
'Using "-" to read the password from stdin '
'will continue to work for the new few releases for backward '
'compatibility, then will be removed. '
'See the man page.' % self.option_strings)
msg2 = ('Deprecated. It is insecure to pass a password on the '
'command line. Use --keycloak-admin-password-file or the '
'KEYCLOAK_ADMIN_PASSWORD environment variable instead. '
'See the man page.')
if values == '-':
print(msg1, file=sys.stderr)
setattr(namespace, self.dest, values)
else:
raise argparse.ArgumentError(self, msg2)

# -----------------------------------------------------------------------------


Expand Down Expand Up @@ -941,8 +964,19 @@ def main():
group.add_argument('-u', '--keycloak-admin-username', default='admin',
help='admin user name (default: admin)')

group.add_argument('-P', '--keycloak-admin-password-file',
type=argparse.FileType('rb'),
help=('file containing admin password '
'(or use a hyphen "-" to read the password '
'from stdin)'))

group.add_argument('-p', '--keycloak-admin-password',
help='admin password (use - to read from stdin)')
action=DeprecatedPasswordAction,
dest='deprecated_keycloak_admin_password',
help=('Deprecated. Use --keycloak-admin-password-file '
'instead. During transition perion this argument '
'will continue to accept a hyphen to indicate '
'the password will be read read from stdin)'))

group.add_argument('--keycloak-admin-realm',
default='master',
Expand Down Expand Up @@ -1020,17 +1054,34 @@ def main():

# ===== Options requiring special handling =====

options.keycloak_admin_password = None
if options.keycloak_auth_role in ['root-admin', 'realm-admin']:

# We need the admin password for this role

# 1. Try password file
if options.keycloak_admin_password_file is not None:
options.keycloak_admin_password = options.keycloak_admin_password_file.readline().strip()
options.keycloak_admin_password_file.close()

# 2. Try KEYCLOAK_ADMIN_PASSWORD environment variable
if options.keycloak_admin_password is None:
if (('KEYCLOAK_ADMIN_PASSWORD' in os.environ) and
(os.environ['KEYCLOAK_ADMIN_PASSWORD'])):
options.keycloak_admin_password = (
os.environ['KEYCLOAK_ADMIN_PASSWORD'])
else:
options.keycloak_admin_password = getpass.getpass(
'%s password: ' % (options.keycloak_admin_username))
elif options.keycloak_admin_password == '-':
options.keycloak_admin_password = sys.stdin.readline().rstrip('\n')

# 3. Try deprecated keycloak-admin-password, accept only hyphen for stdin
try:
if options.deprecated_keycloak_admin_password == '-':
options.keycloak_admin_password = sys.stdin.readline().strip()
except AttributeError:
pass

# 4. Try prompting for the password from the terminal
if options.keycloak_admin_password is None:
options.keycloak_admin_password = getpass.getpass(
'enter %s password: ' % (options.keycloak_admin_username))

if not options.keycloak_admin_password:
parser.error('argument %s is required '
Expand Down
42 changes: 40 additions & 2 deletions doc/keycloak-httpd-client-install.8
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Tools to configure Apache HTTPD as Keycloak client
\fB\-s\fR \fIKEYCLOAK_SERVER_URL\fR
[\fB\-a\fR \fIroot\-admin|realm\-admin|anonymous\fR]
[\fB\-u\fR \fIKEYCLOAK_ADMIN_USERNAME\fR]
\fB\-P\fR \fIKEYCLOAK_ADMIN_PASSWORD_FILE\fR
\fB\-p\fR \fIKEYCLOAK_ADMIN_PASSWORD\fR
[\fB\-\-keycloak\-admin\-realm\fR \fIKEYCLOAK_ADMIN_REALM\fR]
[\fB\-\-initial\-access\-token\fR \fIINITIAL_ACCESS_TOKEN\fR]
Expand Down Expand Up @@ -116,9 +117,21 @@ authenticating as what type of user
admin user name
(default: admin)
.TP
.BR \-P ", " \-\-keycloak\-admin\-password\-file " " \fIKEYCLOAK_ADMIN_PASSWORD_FILE\fR
file containing the admin password (or use a hyphen "-" to indicate the
password will be read from stdin)
(default: None)
.TP
.BR \-p ", " \-\-keycloak\-admin\-password " " \fIKEYCLOAK_ADMIN_PASSWORD\fR
admin password (use - to read from stdin)
admin password (or use a hyphen "-" to to indicate the password will read
from stdin)
(default: None)
\fB Deprecated.\fR It is insecure to pass a password on the command line.
Use \fI\-\-keycloak\-admin\-password\-file\fR instead. \fBIt is no longer
possible to use this argument to pass a password.\fR During a transition
period this argument will continue to accept a hyphen to indicate the
password should be read from stdin in a manner identical to
\fI\-\-keycloak\-admin\-password\-file\fR.
.TP
.BR \-\-keycloak\-admin\-realm " " \fIKEYCLOAK_ADMIN_REALM\fR
realm admin belongs to
Expand Down Expand Up @@ -186,6 +199,31 @@ Web location to protect with Mellon. May be specified multiple times

\fBkeycloak\-httpd\-client\-install\fR will configure a node running Apache with mod_auth_mellon as SAML Service Provider (\fBSP\fR) utilizing a \fBKeycloak\fR server as an Identity Provider(\fBIdP\fR).

.PP
.B How to pass the Keycloak admin password

.PP
The Keycloak admin password may be passed via one of the possible ways listed
here in the order the tool looks for the password.

.PP
\fB1.\fR Try the \fI\-\-keycloak\-admin\-password\-file\fR argument.
If it's a hyphen read the password from stdin, otherwise treat the argument
as the name of a file, open the file and read the password from the file.

.PP
\fB2.\fR Test for the existence of the \fBKEYCLOAK_ADMIN_PASSWORD\fR
environment variable. If the \fBKEYCLOAK_ADMIN_PASSWORD\fR is defined
read the password from it.

.PP
\fB3.\fR Try the \fBdeprecated\fR \fI\-\-keycloak\-admin\-password\fR argument.
If it's a hyphen read the password from stdin, otherwise return an error
because passwords should never be passed on the command line.

.PP
\fB4.\fR Prompt for the password from the terminal.

.PP
.B Authentication Levels and Permissions

Expand Down Expand Up @@ -281,7 +319,7 @@ The REST API may be used by the following authentication roles:
\fB*\fR Connect to Keycloak Server.
.RS
.PP
A session is established with the Keycloak server. OAuth2 is used to log in as the admin user using the \fB\-\-keycloak\-admin\-username\fR and \fB\-\-keycloak\-admin\-password\fR options. The Keycloak server is identified by the \fB\-keycloak\-server\-url\fR option. This step is performed first to assure the remaining steps can complete successfully. A session is maintained for efficiency reasons. You may also need to specify \fB\-\-keycloak\-admin\-role\fR and \fB\-\-keycloak\-admin\-realm\fR to indicate the privilege level you are authenticating with. An anonymous auth role connects to the Keycloak service without any authentication.
A session is established with the Keycloak server. OAuth2 is used to log in as the admin user using the \fB\-\-keycloak\-admin\-username\fR and \fB\-\-keycloak\-admin\-password\-file\fR options. The Keycloak server is identified by the \fB\-keycloak\-server\-url\fR option. This step is performed first to assure the remaining steps can complete successfully. A session is maintained for efficiency reasons. You may also need to specify \fB\-\-keycloak\-admin\-role\fR and \fB\-\-keycloak\-admin\-realm\fR to indicate the privilege level you are authenticating with. An anonymous auth role connects to the Keycloak service without any authentication.
.RE

.PP
Expand Down
24 changes: 20 additions & 4 deletions keycloak_httpd_client/keycloak_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ def do_client_test(options, conn):
For example to delete the client XYZ in the realm ABC:
{prog_name} -s http://example.com:8080 -p password client delete -r ABC -c XYZ
echo password | {prog_name} -s http://example.com:8080 -P - client delete -r ABC -c XYZ
where 'client' is the noun, 'delete' is the verb and -r ABC -c XYZ are
arguments to the delete action.
Expand Down Expand Up @@ -895,9 +895,11 @@ def main():
default='admin',
help='admin user name (default: admin)')

group.add_argument('-p', '--admin-password',
required=True,
help='admin password')
group.add_argument('-P', '--admin-password-file',
type=argparse.FileType('rb'),
help=('file containing admin password '
'(or use a hyphen "-" to read the password '
'from stdin)'))

group.add_argument('--admin-realm',
default='master',
Expand Down Expand Up @@ -995,6 +997,20 @@ def main():
if options.permit_insecure_transport:
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

# Get admin password
options.admin_password = None

# 1. Try password file
if options.admin_password_file is not None:
options.admin_password = options.keycloak_admin_password_file.readline().strip()
options.keycloak_admin_password_file.close()

# 2. Try KEYCLOAK_ADMIN_PASSWORD environment variable
if options.admin_password is None:
if (('KEYCLOAK_ADMIN_PASSWORD' in os.environ) and
(os.environ['KEYCLOAK_ADMIN_PASSWORD'])):
options.admin_password = os.environ['KEYCLOAK_ADMIN_PASSWORD']

try:
anonymous_conn = KeycloakAnonymousConnection(options.server,
options.tls_verify)
Expand Down

0 comments on commit c3121b2

Please sign in to comment.