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

Refactor GlideinWMS Credentials #363

Open
wants to merge 4 commits into
base: branch_v3_11
Choose a base branch
from
Open
Show file tree
Hide file tree
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
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
BrunoCoimbra marked this conversation as resolved.
Show resolved Hide resolved
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
BrunoCoimbra marked this conversation as resolved.
Show resolved Hide resolved
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
56 changes: 24 additions & 32 deletions factory/glideFactoryCredentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from glideinwms.lib import condorMonitor, logSupport
from glideinwms.lib.defaults import force_bytes
from glideinwms.lib.util import is_str_safe

from . import glideFactoryInterface, glideFactoryLib

Expand Down Expand Up @@ -51,7 +52,7 @@ def add_security_credential(self, cred_type, filename):
"""
Adds a security credential.
"""
if not glideFactoryLib.is_str_safe(filename):
if not is_str_safe(filename):
return False

cred_fname = os.path.join(self.cred_dir, "credential_%s" % filename)
Expand Down Expand Up @@ -283,11 +284,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 +308,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
Loading