Add possibility to configure email_domains and scope parameters in oauth2 sso configuration
Fixes: https://tracker.ceph.com/issues/75020
mgr/dashboard: Fix oauth2 sso missing jti claim review
Fixes: https://tracker.ceph.com/issues/75022
mgr/dashboard: Improve oauth2 sso configuration
Add possibility to configure ssl_insecure_skip_verify parameter in oauth2 sso configuration
Fixes: https://tracker.ceph.com/issues/75984
Signed-off-by: Alexandre Jardon <alexandre.jardon@webalex.eu>
* High-availability configurations for `oauth2-proxy` itself are not supported.
* Proper configuration of the IDP and OAuth2 parameters is crucial to avoid authentication failures. Misconfigurations can lead to access issues.
+* IDP must include the jti claim in the issued JWT token because the Ceph Dashboard relies on this value to verify the token's validity.
Container images
svc_spec = cast(OAuth2ProxySpec, self.mgr.spec_store[daemon_spec.service_name].spec)
allowlist_domains = copy(svc_spec.allowlist_domains) or []
allowlist_domains += self.get_service_ips_and_hosts('mgmt-gateway')
+ email_domains = copy(svc_spec.email_domains) or []
context = {
'spec': svc_spec,
'cookie_secret': svc_spec.cookie_secret,
'allowlist_domains': allowlist_domains,
+ 'email_domains': email_domains,
'redirect_url': svc_spec.redirect_url or self.get_redirect_url()
}
{% if redirect_url %}
redirect_url= "{{ redirect_url }}"
{% endif %}
+{% if spec.scope %}
+scope= "{{ spec.scope }}"
+{% endif %}
+{% if spec.ssl_insecure_skip_verify %}
ssl_insecure_skip_verify=true
+{% endif %}
# following configuration is needed to avoid getting Forbidden
# when using chrome like browsers as they handle 3rd party cookies
# Secret value for encrypting cookies.
cookie_secret= "{{ cookie_secret }}"
-email_domains= "*"
+email_domains= "{{ (email_domains | join(',')) or '*' }}"
whitelist_domains= "{{ allowlist_domains | join(',') }}"
oidc_issuer_url= "http://192.168.10.10:8888/dex"
redirect_url= "{redirect_url}"
- ssl_insecure_skip_verify=true
# following configuration is needed to avoid getting Forbidden
# when using chrome like browsers as they handle 3rd party cookies
}
</ng-template>
</div>
+ <!-- Scope -->
+ <div class="form-item">
+ <cd-text-label-list formControlName="scope"
+ label="Scope"
+ helperText="OAuth scope specification."
+ placeholder="openid profile email">
+ </cd-text-label-list>
+ </div>
+ <!-- Email_domains -->
+ <div class="form-item">
+ <cd-text-label-list formControlName="email_domains"
+ label="Email domains"
+ helperText="Email domains to be allowed."
+ placeholder="*">
+ </cd-text-label-list>
+ </div>
<!-- Allowlist_domains -->
<div class="form-item">
<cd-text-label-list formControlName="allowlist_domains"
placeholder="192.168.100.1:8080">
</cd-text-label-list>
</div>
+ <!-- SSL Insecure Skip Verify -->
+ <div class="form-item">
+ <cds-checkbox i18n-label
+ formControlName="ssl_insecure_skip_verify">
+ Skip TLS verification
+ <cd-help-text i18n>
+ Skip TLS certificate verification for the OIDC provider. Use only in non-production environments.
+ </cd-help-text>
+ </cds-checkbox>
+ </div>
}
@if (!serviceForm.controls.unmanaged.value && ['mgmt-gateway'].includes(serviceForm.controls.service_type.value))
],
https_address: [null, [CdValidators.oauthAddressTest()]],
redirect_url: [null],
- allowlist_domains: [null]
+ scope: [null],
+ email_domains: [null],
+ allowlist_domains: [null],
+ ssl_insecure_skip_verify: [false]
});
}
'client_secret',
'oidc_issuer_url',
'redirect_url',
- 'allowlist_domains'
+ 'scope',
+ 'email_domains',
+ 'allowlist_domains',
+ 'ssl_insecure_skip_verify'
];
oauth2SpecKeys.forEach((key) => {
this.serviceForm.get(key).setValue(response[0].spec[key]);
serviceSpec['oidc_issuer_url'] = values['oidc_issuer_url']?.trim();
serviceSpec['https_address'] = values['https_address']?.trim();
serviceSpec['redirect_url'] = values['redirect_url']?.trim();
+ serviceSpec['scope'] = values['scope']?.join(' ');
+ if (values['email_domains']) {
+ serviceSpec['email_domains'] = values['email_domains']?.map((emailDomain: string) => {
+ return emailDomain.trim();
+ });
+ }
if (values['allowlist_domains']) {
serviceSpec['allowlist_domains'] = values['allowlist_domains']?.map(
(allowlistDomain: string) => {
}
);
}
+ serviceSpec['ssl_insecure_skip_verify'] = values['ssl_insecure_skip_verify'];
if (values['ssl']) {
this.applySslCertificateConfig(serviceSpec, values);
}
def get_user(cls, token):
try:
dtoken = cls.decode_token(token)
- if 'jti' in dtoken and not cls.is_blocklisted(dtoken['jti']):
- user = AuthManager.get_user(dtoken['username'])
- if 'iat' in dtoken and user.last_update <= dtoken['iat']:
- return user
- cls.logger.debug( # type: ignore
- "user info changed after token was issued, iat=%s last_update=%s",
- dtoken['iat'], user.last_update
- )
+ if 'jti' in dtoken:
+ if not cls.is_blocklisted(dtoken['jti']):
+ user = AuthManager.get_user(dtoken['username'])
+ if 'iat' in dtoken and user.last_update <= dtoken['iat']:
+ return user
+ cls.logger.debug( # type: ignore
+ "user info changed after token was issued, iat=%s last_update=%s",
+ dtoken['iat'], user.last_update
+ )
+ else:
+ cls.logger.debug('Token is block-listed') # type: ignore
else:
- cls.logger.debug('Token is block-listed') # type: ignore
+ cls.logger.debug('Missing jti claim in token') # type: ignore
except ExpiredSignatureError:
cls.logger.debug("Token has expired") # type: ignore
except InvalidTokenError:
client_secret: Optional[str] = None,
oidc_issuer_url: Optional[str] = None,
redirect_url: Optional[str] = None,
+ scope: Optional[str] = None,
cookie_secret: Optional[str] = None,
ssl_cert: Optional[str] = None,
ssl_key: Optional[str] = None,
ssl: Optional[bool] = True,
certificate_source: Optional[str] = None,
custom_sans: Optional[List[str]] = None,
+ email_domains: Optional[List[str]] = None,
allowlist_domains: Optional[List[str]] = None,
+ ssl_insecure_skip_verify: Optional[bool] = False,
unmanaged: bool = False,
extra_container_args: Optional[GeneralArgList] = None,
extra_entrypoint_args: Optional[GeneralArgList] = None,
#: The URL oauth2-proxy will redirect to after a successful login. If not provided
# cephadm will calculate automatically the value of this url.
self.redirect_url = redirect_url
+ #: OAuth scope specification.
+ # Default list of scopes will be used in case no scope is configured.
+ self.scope = scope
#: The secret key used for signing cookies. Its length must be 16,
# 24, or 32 bytes to create an AES cipher.
self.cookie_secret = cookie_secret or self.generate_random_secret()
+ #: List of allowed email domains.
+ self.email_domains = email_domains
#: List of allowed domains for safe redirection after login or logout,
# preventing unauthorized redirects.
self.allowlist_domains = allowlist_domains
+ #: Skip TLS verification for the OIDC provider. Use only in non-production environments.
+ self.ssl_insecure_skip_verify = ssl_insecure_skip_verify
self.unmanaged = unmanaged
def generate_random_secret(self) -> str:
self._validate_url(self.oidc_issuer_url, "oidc_issuer_url")
if self.redirect_url is not None:
self._validate_url(self.redirect_url, "redirect_url")
+ if self.scope is not None:
+ self._validate_non_empty_string(self.scope, "scope")
+ if self.email_domains is not None:
+ self._validate_domain_name(self.email_domains, "email_domains")
if self.https_address is not None:
self._validate_https_address(self.https_address)
if not all([result.scheme, result.netloc]):
raise SpecValidationError(f"Error parsing {field_name} field: Must be a valid URL.")
+ def _validate_domain_name(self, domains: Optional[List[str]], field_name: str) -> None:
+ from urllib.parse import urlparse
+ for domain in (domains or []):
+ try:
+ result = urlparse(f"http://{domain}")
+ except Exception as e:
+ raise SpecValidationError(
+ f"Invalid {field_name}: {e}. Must be a valid domain name."
+ )
+ else:
+ if result.netloc != domain:
+ raise SpecValidationError(
+ f"Invalid {field_name}: '{domain}' is not a valid domain name. "
+ f"Must be a valid domain (e.g., 'domain.test')."
+ )
+
def _validate_https_address(self, https_address: Optional[str]) -> None:
from urllib.parse import urlparse
result = urlparse(f'http://{https_address}')