Skip to content

Commit

Permalink
Refactor GlideinWMS Credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
BrunoCoimbra committed Jul 9, 2024
1 parent 37b732c commit 4d6021c
Show file tree
Hide file tree
Showing 23 changed files with 3,411 additions and 516 deletions.
4 changes: 2 additions & 2 deletions creation/lib/cgWCreate.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def populate(self, exe_fname, entry_name, conf, entry):
submit_attrs = entry.get_child("config").get_child("submit").get_child_list("submit_attrs")
enc_input_files = []

enc_input_files.append("$ENV(IDTOKENS_FILE:)")
enc_input_files.append("$ENV(IDENTITY_CREDENTIALS:)")
self.add_environment("IDTOKENS_FILE=$ENV(IDTOKENS_FILE:)")

if gridtype not in ["ec2", "gce"] and not (gridtype == "arc" and auth_method == "grid_proxy"):
Expand Down Expand Up @@ -308,7 +308,7 @@ def populate_standard_grid(self, rsl, auth_method, gridtype, entry_enabled, entr
self.add("cream_attributes", "$ENV(GLIDEIN_RSL)")
elif gridtype == "nordugrid" and rsl:
self.add("nordugrid_rsl", "$ENV(GLIDEIN_RSL)")
elif (gridtype == "condor") and ("project_id" in auth_method):
elif (gridtype == "condor") and ("project_id" in auth_method): # TODO: Check for credentials refactoring impact
self.add("+ProjectName", '"$ENV(GLIDEIN_PROJECT_ID)"')

# Force the copy to spool to prevent caching at the CE side
Expand Down
5 changes: 4 additions & 1 deletion creation/lib/cvWParamDict.py
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,8 @@ def populate_common_descript(descript_dict, params):
"security_class": "ProxySecurityClasses",
"trust_domain": "ProxyTrustDomains",
"type": "ProxyTypes",
"purpose": "CredentialPurposes",
"context": "CredentialContexts",
# credential files probably should be handles as a list, each w/ name and path
# or the attributes ending in _file are files
# "file": "CredentialFiles", # placeholder for when name will not be absfname
Expand Down Expand Up @@ -1186,6 +1188,7 @@ def populate_common_descript(descript_dict, params):
descript_dict.add(proxy_attr_names[attr], repr(proxy_descript_values[attr]))

match_expr = params.match.match_expr
descript_dict.add("Parameters", repr(params.security.parameters))
descript_dict.add("MatchExpr", match_expr)


Expand Down Expand Up @@ -1345,7 +1348,7 @@ def populate_group_security(client_security, params, sub_params, group_name):
client_security["schedd_DNs"] = schedd_dns

pilot_dns = []
exclude_from_pilot_dns = ["SCITOKEN", "IDTOKEN"]
exclude_from_pilot_dns = ["SCITOKEN", "IDTOKEN", "GENERATOR"]
for credentials in (params.security.credentials, sub_params.security.credentials):
if is_true(params.groups[group_name].enabled):
for pel in credentials:
Expand Down
36 changes: 36 additions & 0 deletions creation/lib/cvWParams.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,18 @@ def init_defaults(self):
"Type of credential: grid_proxy,cert_pair,key_pair,username_password,auth_file",
None,
)
proxy_defaults["purpose"] = (
"request",
"credential purpose",
"Purpose of credential: request,payload",
None,
)
proxy_defaults["context"] = (
None,
"PythonExpr",
"Python mapping with a context for credential generators",
None,
)
proxy_defaults["trust_domain"] = ("OSG", "grid_type", "Trust Domain", None)
proxy_defaults["creation_script"] = (None, "command", "Script to re-create credential", None)
proxy_defaults["update_frequency"] = (None, "int", "Update proxy when there is this much time left", None)
Expand Down Expand Up @@ -332,6 +344,23 @@ def init_defaults(self):
proxy_defaults["vm_type_fname"] = (None, "fname", "to specify a vm type without reconfig", None)
proxy_defaults["project_id"] = (None, "string", "OSG Project ID. Ex TG-12345", None)

# Parameter settings
parameter_defaults = cWParams.CommentedOrderedDict()
parameter_defaults["name"] = (None, "string", "parameter name", None)
parameter_defaults["value"] = (None, "string", "parameter value", None)
parameter_defaults["type"] = (
None,
"string",
"parameter type (int, string, expr)",
None,
)
parameter_defaults["context"] = (
None,
"PythonExpr",
"Python mapping with a context for parameter generators",
None,
)

security_defaults = cWParams.CommentedOrderedDict()
security_defaults["proxy_selection_plugin"] = (
None,
Expand All @@ -345,6 +374,12 @@ def init_defaults(self):
"Each credential element contains",
proxy_defaults,
)
security_defaults["parameters"] = (
OrderedDict(),
"List of parameters",
"Each parameter element contains",
parameter_defaults,
)
security_defaults["security_name"] = (
None,
"frontend_name",
Expand Down Expand Up @@ -715,6 +750,7 @@ def get_xml_format(self):
"attrs": {"el_name": "attr", "subtypes_params": {"class": {}}},
"groups": {"el_name": "group", "subtypes_params": {"class": {}}},
"match_attrs": {"el_name": "match_attr", "subtypes_params": {"class": {}}},
"parameters": {"el_name": "parameter", "subtypes_params": {"class": {}}},
},
}

Expand Down
33 changes: 33 additions & 0 deletions creation/web_base/setup_x509.sh
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,32 @@ copy_idtokens() {
return
}

# Copy non-idtoken credentials from the start directory to the credentials directory
# Credentials must match: ^credential_.*\.(scitoken|jwt|pem|rsa|txt)$
copy_credentials() {
local start_dir from_dir=$1 to_dir=$2
start_dir=$(pwd)
if ! cd "$from_dir"; then
ERROR="Cannot cd to from_dir ($from_dir)"
# did not change directory, OK to just return
return 1
fi
for cred in credential_*; do
[[ -e "$cred" ]] || continue # protect against nullglob (no match)
if [[ "$cred" =~ ^credential_.*\.(scitoken|jwt|pem|rsa|txt)$ ]]; then
if cp "$cred" "$to_dir/$cred"; then
warn "Copied credential '${cred}' to '${to_dir}/'"
else
warn "Failed to copy credential '${cred}'"
fi
else
warn "Skipping credential '${cred}'"
fi
done
cd "$start_dir" || true
return
}

# Retrieve trust domain
# Uses TRUST_DOMAIN, GLIDEIN_Collector and CCB_ADDRESS from glidein_config
# Return only the first Collector if more are in the list (separators:,\ \t)
Expand Down Expand Up @@ -457,6 +483,13 @@ _main() {
else
warn "$ERROR"
fi
# TODO: Initial copy. Evaluate separately credentials refresh.
# PAYLOAD CREDENTIALS
if copy_credentials "$GLIDEIN_START_DIR_ORIG" "${gwms_credentials_dir}"; then
cred_updated+=credentials,
else
warn "$ERROR"
fi

# x509 - skip if there is no X509_USER_PROXY
if ! X509_USER_PROXY=$(get_x509_proxy); then
Expand Down
12 changes: 6 additions & 6 deletions factory/glideFactoryConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import os.path
import shutil

from glideinwms.lib import pubCrypto, symCrypto
from glideinwms.lib import credentials, pubCrypto, symCrypto

############################################################
#
Expand Down Expand Up @@ -135,7 +135,7 @@ def __init__(self, entry_name, config_file, convert_function=repr):
############################################################


class GlideinKey:
class GlideinKey: # TODO: Check for credentials refactor
def __init__(self, pub_key_type, key_fname=None, recreate=False):
self.pub_key_type = pub_key_type
self.load(key_fname, recreate)
Expand Down Expand Up @@ -247,7 +247,7 @@ def load_old_rsa_key(self):

if self.data["OldPubKeyType"] is not None:
try:
self.data["OldPubKeyObj"] = GlideinKey(self.data["OldPubKeyType"], key_fname=self.backup_rsakey_fname)
self.data["OldPubKeyObj"] = credentials.RSAKey(path=self.backup_rsakey_fname)
except Exception:
self.data["OldPubKeyType"] = None
self.data["OldPubKeyObj"] = None
Expand All @@ -271,9 +271,9 @@ def load_pub_key(self, recreate=False):
recreate (bool): Create a new key overwriting the old one. Defaults to False
"""
if self.data["PubKeyType"] is not None:
self.data["PubKeyObj"] = GlideinKey(
self.data["PubKeyType"], key_fname=self.default_rsakey_fname, recreate=recreate
)
self.data["PubKeyObj"] = credentials.RSAKey(path=self.default_rsakey_fname)
if recreate:
self.data["PubKeyObj"].recreate()
else:
self.data["PubKeyObj"] = None
return
Expand Down
53 changes: 22 additions & 31 deletions factory/glideFactoryCredentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,12 @@ def check_security_credentials(auth_method, params, client_int_name, entry_name,
CredentialError: if the credentials in params don't match what is defined for the auth method
"""

auth_method_list = auth_method.split("+")
if not set(auth_method_list) & set(SUPPORTED_AUTH_METHODS):
# TODO: This function policies need to be reviewed and updated.

auth_set = params.get("AuthSet", auth_method.split("+")) # Fall back to auth_method (str) for retrocompatibility
if isinstance(auth_set, str) and not set(auth_set) & set(SUPPORTED_AUTH_METHODS):
logSupport.log.warning(
"None of the supported auth methods %s in provided auth methods: %s"
% (SUPPORTED_AUTH_METHODS, auth_method_list)
f"None of the supported auth methods {SUPPORTED_AUTH_METHODS} in provided auth methods: {auth_set}"
)
return

Expand All @@ -306,102 +307,92 @@ def check_security_credentials(auth_method, params, client_int_name, entry_name,
"AuthFile",
}

if "scitoken" in auth_method_list or "frontend_scitoken" in params and scitoken_passthru:
if "scitoken" in auth_set or "frontend_scitoken" in params and scitoken_passthru:
# TODO check validity
# TODO Specifically, Add checks that no undesired credentials are
# sent also when token is used
return
if "grid_proxy" in auth_method_list:
if "grid_proxy" in auth_set:
if not scitoken_passthru:
if "SubmitProxy" in params:
# v3+ protocol
valid_keys = {"SubmitProxy"}
invalid_keys = relevant_keys.difference(valid_keys)
if params_keys.intersection(invalid_keys):
raise CredentialError(
"Request from %s has credentials not required by the entry %s, skipping request"
% (client_int_name, entry_name)
f"Request from {client_int_name} has credentials not required by the entry {entry_name}, skipping request"
)
else:
# No proxy sent
raise CredentialError(
"Request from client %s did not provide a proxy as required by the entry %s, skipping request"
% (client_int_name, entry_name)
f"Request from client {client_int_name} did not provide a proxy as required by the entry {entry_name}, skipping request"
)

else:
# Only v3+ protocol supports non grid entries
# Verify that the glidein proxy was provided for non-proxy auth methods
if "GlideinProxy" not in params and not scitoken_passthru:
raise CredentialError("Glidein proxy cannot be found for client %s, skipping request" % client_int_name)
raise CredentialError(f"Glidein proxy cannot be found for client {client_int_name}, skipping request")

if "cert_pair" in auth_method_list:
if "cert_pair" in auth_set:
# Validate both the public and private certs were passed
if not (("PublicCert" in params) and ("PrivateCert" in params)):
# if not ('PublicCert' in params and 'PrivateCert' in params):
# cert pair is required, cannot service request
raise CredentialError(
"Client '%s' did not specify the certificate pair in the request, this is required by entry %s, skipping "
% (client_int_name, entry_name)
f"Client '{client_int_name}' did not specify the certificate pair in the request, this is required by entry {entry_name}, skipping"
)
# Verify no other credentials were passed
valid_keys = {"GlideinProxy", "PublicCert", "PrivateCert", "VMId", "VMType"}
invalid_keys = relevant_keys.difference(valid_keys)
if params_keys.intersection(invalid_keys):
raise CredentialError(
"Request from %s has credentials not required by the entry %s, skipping request"
% (client_int_name, entry_name)
f"Request from {client_int_name} has credentials not required by the entry {entry_name}, skipping request"
)

elif "key_pair" in auth_method_list:
elif "key_pair" in auth_set:
# Validate both the public and private keys were passed
if not (("PublicKey" in params) and ("PrivateKey" in params)):
# key pair is required, cannot service request
raise CredentialError(
"Client '%s' did not specify the key pair in the request, this is required by entry %s, skipping "
% (client_int_name, entry_name)
f"Client '{client_int_name}' did not specify the key pair in the request, this is required by entry {entry_name}, skipping"
)
# Verify no other credentials were passed
valid_keys = {"GlideinProxy", "PublicKey", "PrivateKey", "VMId", "VMType"}
invalid_keys = relevant_keys.difference(valid_keys)
if params_keys.intersection(invalid_keys):
raise CredentialError(
"Request from %s has credentials not required by the entry %s, skipping request"
% (client_int_name, entry_name)
f"Request from {client_int_name} has credentials not required by the entry {entry_name}, skipping request"
)

elif "auth_file" in auth_method_list:
elif "auth_file" in auth_set:
# Validate auth_file is passed
if "AuthFile" not in params:
# auth_file is required, cannot service request
raise CredentialError(
"Client '%s' did not specify the auth_file in the request, this is required by entry %s, skipping "
% (client_int_name, entry_name)
f"Client '{client_int_name}' did not specify the auth_file in the request, this is required by entry {entry_name}, skipping"
)
# Verify no other credentials were passed
valid_keys = {"GlideinProxy", "AuthFile", "VMId", "VMType"}
invalid_keys = relevant_keys.difference(valid_keys)
if params_keys.intersection(invalid_keys):
raise CredentialError(
"Request from %s has credentials not required by the entry %s, skipping request"
% (client_int_name, entry_name)
f"Request from {client_int_name} has credentials not required by the entry {entry_name}, skipping request"
)

elif "username_password" in auth_method_list:
elif "username_password" in auth_set:
# Validate username and password keys were passed
if not (("Username" in params) and ("Password" in params)):
# username and password is required, cannot service request
raise CredentialError(
"Client '%s' did not specify the username and password in the request, this is required by entry %s, skipping "
% (client_int_name, entry_name)
f"Client '{client_int_name}' did not specify the username and password in the request, this is required by entry {entry_name}, skipping request"
)
# Verify no other credentials were passed
valid_keys = {"GlideinProxy", "Username", "Password", "VMId", "VMType"}
invalid_keys = relevant_keys.difference(valid_keys)
if params_keys.intersection(invalid_keys):
raise CredentialError(
"Request from %s has credentials not required by the entry %s, skipping request"
% (client_int_name, entry_name)
f"Request from {client_int_name} has credentials not required by the entry {entry_name}, skipping request"
)

else:
Expand Down
Loading

0 comments on commit 4d6021c

Please sign in to comment.