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

#1946 fix that alternative OIDC provider for "devops" authorization could not be configured #1948

Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion deployment/helm/ditto/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ description: |
A digital twin is a virtual, cloud based, representation of his real world counterpart
(real world “Things”, e.g. devices like sensors, smart heating, connected cars, smart grids, EV charging stations etc).
type: application
version: 3.5.6 # chart version is effectively set by release-job
version: 3.5.7-0 # chart version is effectively set by release-job
appVersion: 3.5.6
keywords:
- iot-chart
Expand Down
14 changes: 6 additions & 8 deletions deployment/helm/ditto/templates/gateway-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ spec:
"{{ printf "%s%s%s%d=%s" "-Dditto.gateway.authentication.devops.oauth.openid-connect-issuers." $key ".auth-subjects." $index $subject }}"
{{- end }}
{{- end }}
{{- range $index, $oauthSubject := .Values.gateway.config.authentication.devops.oauthSubjects }}
"{{ printf "%s%d=%s" "-Dditto.gateway.authentication.devops.devops-oauth2-subjects." $index $oauthSubject }}"
{{- end }}
{{- range $index, $oauthSubject := .Values.gateway.config.authentication.devops.statusOauthSubjects }}
"{{ printf "%s%d=%s" "-Dditto.gateway.authentication.devops.status-oauth2-subjects." $index $oauthSubject }}"
{{- end }}
{{ join " " .Values.gateway.systemProps }}
- name: CLUSTER_BS_REQUIRED_CONTACTS
value: "{{ .Values.global.cluster.requiredContactPoints }}"
Expand Down Expand Up @@ -191,10 +197,6 @@ spec:
secretKeyRef:
name: {{ .Values.gateway.config.authentication.devops.existingSecret | default ( printf "%s-gateway-secret" ( include "ditto.fullname" . )) }}
key: devops-password
{{- range $index, $oauthSubject := .Values.gateway.config.authentication.devops.oauthSubjects }}
- name: DEVOPS_OAUTH2_SUBJECTS.{{ $index }}
value: "{{ $oauthSubject }}"
{{- end }}
- name: DEVOPS_STATUS_SECURED
value: "{{ .Values.gateway.config.authentication.devops.statusSecured }}"
- name: STATUS_AUTHENTICATION_METHOD
Expand All @@ -204,10 +206,6 @@ spec:
secretKeyRef:
name: {{ .Values.gateway.config.authentication.devops.existingSecret | default ( printf "%s-gateway-secret" ( include "ditto.fullname" . )) }}
key: status-password
{{- range $index, $oauthSubject := .Values.gateway.config.authentication.devops.statusOauthSubjects }}
- name: STATUS_OAUTH2_SUBJECTS.{{ $index }}
value: "{{ $oauthSubject }}"
{{- end }}
- name: WS_SUBSCRIBER_BACKPRESSURE
value: "{{ .Values.gateway.config.websocket.subscriber.backpressureQueueSize }}"
- name: WS_PUBLISHER_BACKPRESSURE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@

import javax.annotation.Nullable;

import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ReceiveTimeout;
import org.apache.pekko.japi.pf.ReceiveBuilder;
import org.apache.pekko.pattern.Patterns;
import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.headers.WithDittoHeaders;
Expand All @@ -43,19 +48,13 @@
import org.eclipse.ditto.connectivity.model.signals.commands.query.RetrieveConnectionsResponse;
import org.eclipse.ditto.gateway.service.util.config.DittoGatewayConfig;
import org.eclipse.ditto.gateway.service.util.config.endpoints.CommandConfig;
import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig;
import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory;
import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger;
import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig;
import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonCollectors;
import org.eclipse.ditto.json.JsonFieldSelector;

import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ReceiveTimeout;
import org.apache.pekko.japi.pf.ReceiveBuilder;
import org.apache.pekko.pattern.Patterns;


/**
* Abstract actor for retrieving multiple connections.
Expand Down Expand Up @@ -195,20 +194,19 @@ private CompletionStage<RetrieveConnectionResponse> retrieveConnection(final Ret

private CompletionStage<RetrieveConnectionResponse> askConnectivity(final RetrieveConnection command) {

logger.withCorrelationId(command).debug("Sending command <{}> to connectivity.service.", command);
final ThreadSafeDittoLogger log = logger.withCorrelationId(command);
log.debug("Sending command <{}> to connectivity.service.", command);
final var commandWithCorrelationId = ensureCommandHasCorrelationId(command);
Duration askTimeout = initialCommand.getDittoHeaders().getTimeout().orElse(defaultTimeout);
return Patterns.ask(edgeCommandForwarder, commandWithCorrelationId, askTimeout)
.thenApply(response -> {
logger.withCorrelationId(command)
.debug("Received response <{}> from connectivity.service.", response);
log.debug("Received response <{}> from connectivity service", response);
throwCauseIfErrorResponse(response);
throwCauseIfDittoRuntimeException(response);
final RetrieveConnectionResponse mappedResponse =
mapToType(response, RetrieveConnectionResponse.class, command);
logger.withCorrelationId(command)
.info("Received response of type <{}> from connectivity.service.",
mappedResponse.getType());
log.info("Received response of type <{}> for id <{}> from connectivity service",
mappedResponse.getType(), mappedResponse.getEntityId());
return mappedResponse;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,21 @@
import java.util.Optional;
import java.util.function.Function;

import org.eclipse.ditto.gateway.service.util.config.security.DevOpsConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.pekko.http.javadsl.server.Directives;
import org.apache.pekko.http.javadsl.server.Route;
import org.apache.pekko.http.javadsl.server.directives.SecurityDirectives;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.gateway.service.util.config.security.DevOpsConfig;
import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory;
import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger;

/**
* Custom Pekko Http directive performing basic auth for a defined {@link #USER_DEVOPS devops user}.
*/
public final class DevOpsBasicAuthenticationDirective implements DevopsAuthenticationDirective {

private static final Logger LOGGER = LoggerFactory.getLogger(DevOpsBasicAuthenticationDirective.class);
private static final ThreadSafeDittoLogger LOGGER =
DittoLoggerFactory.getThreadSafeLogger(DevOpsBasicAuthenticationDirective.class);

private static final String USER_DEVOPS = "devops";

Expand Down Expand Up @@ -65,15 +66,9 @@ public static DevOpsBasicAuthenticationDirective status(final DevOpsConfig devOp
return new DevOpsBasicAuthenticationDirective(devOpsConfig.getPassword(), devOpsConfig.getStatusPassword());
}

/**
* Authenticates the devops resources with the chosen authentication method.
*
* @param realm the realm to apply.
* @param inner the inner route, which will be performed on successful authentication.
* @return the inner route wrapped with authentication.
*/
public Route authenticateDevOps(final String realm, final Route inner) {
LOGGER.debug("DevOps basic authentication is enabled for {}.", realm);
@Override
public Route authenticateDevOps(final String realm, final DittoHeaders dittoHeaders, final Route inner) {
LOGGER.withCorrelationId(dittoHeaders).debug("DevOps basic authentication is enabled for {}.", realm);
return Directives.authenticateBasic(realm, new BasicAuthenticator(passwords), userName -> inner);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
*/
package org.eclipse.ditto.gateway.service.endpoints.directives.auth;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.pekko.http.javadsl.server.Route;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.internal.utils.pekko.logging.DittoLogger;
import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory;

/**
* Authentication directive which does not perform any authentication.
*/
public final class DevOpsInsecureAuthenticationDirective implements DevopsAuthenticationDirective {

private static final Logger LOGGER = LoggerFactory.getLogger(DevOpsInsecureAuthenticationDirective.class);
private static final DittoLogger LOGGER = DittoLoggerFactory.getLogger(DevOpsInsecureAuthenticationDirective.class);
private static final DevOpsInsecureAuthenticationDirective INSTANCE = new DevOpsInsecureAuthenticationDirective();

private DevOpsInsecureAuthenticationDirective() {}
Expand All @@ -32,8 +32,8 @@ public static DevOpsInsecureAuthenticationDirective getInstance() {
}

@Override
public Route authenticateDevOps(final String realm, final Route inner) {
LOGGER.warn("DevOps resource is not secured");
public Route authenticateDevOps(final String realm, final DittoHeaders dittoHeaders, final Route inner) {
LOGGER.withCorrelationId(dittoHeaders).warn("DevOps resource is not secured");
return inner;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
import org.eclipse.ditto.gateway.service.security.authentication.AuthenticationResult;
import org.eclipse.ditto.gateway.service.security.authentication.jwt.JwtAuthenticationProvider;
import org.eclipse.ditto.gateway.service.util.config.security.DevOpsConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory;
import org.eclipse.ditto.internal.utils.pekko.logging.ThreadSafeDittoLogger;

import scala.util.Try;

Expand All @@ -40,7 +40,8 @@
*/
public final class DevOpsOAuth2AuthenticationDirective implements DevopsAuthenticationDirective {

private static final Logger LOGGER = LoggerFactory.getLogger(DevOpsOAuth2AuthenticationDirective.class);
private static final ThreadSafeDittoLogger LOGGER =
DittoLoggerFactory.getThreadSafeLogger(DevOpsOAuth2AuthenticationDirective.class);

/**
* The Http basic auth realm for the "ditto-devops" user used for /devops resource.
Expand Down Expand Up @@ -92,55 +93,55 @@ public static DevOpsOAuth2AuthenticationDirective devops(final DevOpsConfig devO
return new DevOpsOAuth2AuthenticationDirective(jwtAuthenticationProvider, expectedSubjects);
}

/**
* Authenticates the devops resources with the chosen authentication method.
*
* @param realm the realm to apply.
* @param inner the inner route, which will be performed on successful authentication.
* @return the inner route wrapped with authentication.
*/
public Route authenticateDevOps(final String realm, final Route inner) {
LOGGER.debug("DevOps OAuth authentication is enabled for {}.", realm);
@Override
public Route authenticateDevOps(final String realm, final DittoHeaders dittoHeaders, final Route inner) {
final ThreadSafeDittoLogger logger = LOGGER.withCorrelationId(dittoHeaders);
logger.debug("DevOps OAuth authentication is enabled for {}.", realm);
return extractRequestContext(requestContext -> {
final String authorizationHeaderValue = requestContext.getRequest()
.getHeader("authorization")
.map(HttpHeader::value)
.orElse("");
LOGGER.debug("Trying to use OAuth2 authentication for authorization header <{}>", authorizationHeaderValue);
logger.debug("Trying to use OAuth2 authentication for authorization header <{}>", authorizationHeaderValue);
final CompletionStage<AuthenticationResult> authenticationResult =
jwtAuthenticationProvider.authenticate(requestContext, DittoHeaders.empty());
jwtAuthenticationProvider.authenticate(requestContext, dittoHeaders);

final Function<Try<AuthenticationResult>, Route> handleAuthenticationTry =
authenticationResultTry -> handleAuthenticationTry(authenticationResultTry, inner, requestContext);
authenticationResultTry ->
handleAuthenticationTry(authenticationResultTry, dittoHeaders, inner, requestContext);

return Directives.onComplete(authenticationResult, handleAuthenticationTry);
});
}

private Route handleAuthenticationTry(final Try<AuthenticationResult> authenticationResultTry, final Route inner,
private Route handleAuthenticationTry(final Try<AuthenticationResult> authenticationResultTry,
final DittoHeaders dittoHeaders,
final Route inner,
final RequestContext requestContext) {

if (authenticationResultTry.isSuccess()) {
final AuthenticationResult authenticationResult = authenticationResultTry.get();
final ThreadSafeDittoLogger logger = LOGGER.withCorrelationId(dittoHeaders);
if (!authenticationResult.isSuccess()) {
LOGGER.info("DevOps OAuth authentication was not successful for request: '{}' because of '{}'.",
logger.info("DevOps OAuth authentication was not successful for request: '{}' because of '{}'.",
requestContext.getRequest(), authenticationResult.getReasonOfFailure().getMessage());
return Directives.failWith(authenticationResult.getReasonOfFailure());
} else {
final List<String> authorizationSubjectIds =
authenticationResult.getAuthorizationContext().getAuthorizationSubjectIds();
final boolean isAuthorized = expectedSubjects.isEmpty() || authorizationSubjectIds.stream().anyMatch(expectedSubjects::contains);
if (isAuthorized) {
LOGGER.info("DevOps Oauth authentication was successful.");
logger.info("DevOps Oauth authentication was successful, user subjects {} were " +
"part of expected subjects: {}", authorizationSubjectIds, expectedSubjects);
return inner;
} else {
final String message = String.format(
"Unauthorized subject(s): <%s>. Expected: <%s>",
authorizationSubjectIds, expectedSubjects
);
final GatewayAuthenticationFailedException reasonOfFailure =
GatewayAuthenticationFailedException.fromMessage(message, DittoHeaders.empty());
LOGGER.warn("DevOps Oauth authentication failed.", reasonOfFailure);
GatewayAuthenticationFailedException.fromMessage(message, dittoHeaders);
logger.warn("DevOps Oauth authentication failed.", reasonOfFailure);
return Directives.failWith(reasonOfFailure);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.eclipse.ditto.gateway.service.endpoints.directives.auth;

import org.apache.pekko.http.javadsl.server.Route;
import org.eclipse.ditto.base.model.headers.DittoHeaders;


/**
Expand All @@ -21,6 +22,14 @@
@FunctionalInterface
public interface DevopsAuthenticationDirective {

Route authenticateDevOps(final String realm, final Route inner);
/**
* Authenticates the devops resources with the chosen authentication method.
*
* @param realm the realm to apply.
* @param dittoHeaders the DittoHeaders to use for logging.
* @param inner the inner route, which will be performed on successful authentication.
* @return the inner route wrapped with authentication.
*/
Route authenticateDevOps(String realm, DittoHeaders dittoHeaders, Route inner);

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import org.eclipse.ditto.gateway.service.security.authentication.jwt.JwtAuthenticationProvider;
import org.eclipse.ditto.gateway.service.util.config.security.DevOpsConfig;

import com.typesafe.config.Config;

public final class DevopsAuthenticationDirectiveFactory {

private final JwtAuthenticationProvider jwtAuthenticationProvider;
Expand All @@ -29,44 +31,36 @@ private DevopsAuthenticationDirectiveFactory(final JwtAuthenticationProvider jwt
}

public static DevopsAuthenticationDirectiveFactory newInstance(
final JwtAuthenticationFactory jwtAuthenticationFactory, final DevOpsConfig devOpsConfig) {
final JwtAuthenticationFactory jwtAuthenticationFactory, final DevOpsConfig devOpsConfig,
final Config dittoExtensionConfig) {

final JwtAuthenticationProvider jwtAuthenticationProvider = JwtAuthenticationProvider.newInstance(
jwtAuthenticationFactory.newJwtAuthenticationResultProvider(
"ditto.gateway.authentication.devops.oauth"
dittoExtensionConfig, "devops"
),
jwtAuthenticationFactory.getJwtValidator());
jwtAuthenticationFactory.getJwtValidator()
);
return new DevopsAuthenticationDirectiveFactory(jwtAuthenticationProvider, devOpsConfig);
}

public DevopsAuthenticationDirective status() {
if (!devOpsConfig.isSecured() || !devOpsConfig.isStatusSecured()) {
return DevOpsInsecureAuthenticationDirective.getInstance();
}
switch (devOpsConfig.getStatusAuthenticationMethod()) {
case BASIC:
return DevOpsBasicAuthenticationDirective.status(devOpsConfig);
case OAUTH2:
return DevOpsOAuth2AuthenticationDirective.status(devOpsConfig, jwtAuthenticationProvider);
default:
throw new IllegalStateException(
"Unknown devops authentication method: " + devOpsConfig.getStatusAuthenticationMethod());
}
return switch (devOpsConfig.getStatusAuthenticationMethod()) {
case BASIC -> DevOpsBasicAuthenticationDirective.status(devOpsConfig);
case OAUTH2 -> DevOpsOAuth2AuthenticationDirective.status(devOpsConfig, jwtAuthenticationProvider);
};

}

public DevopsAuthenticationDirective devops() {
if (!devOpsConfig.isSecured()) {
return DevOpsInsecureAuthenticationDirective.getInstance();
}
switch (devOpsConfig.getDevopsAuthenticationMethod()) {
case BASIC:
return DevOpsBasicAuthenticationDirective.devops(devOpsConfig);
case OAUTH2:
return DevOpsOAuth2AuthenticationDirective.devops(devOpsConfig, jwtAuthenticationProvider);
default:
throw new IllegalStateException(
"Unknown devops authentication method: " + devOpsConfig.getStatusAuthenticationMethod());
}
return switch (devOpsConfig.getDevopsAuthenticationMethod()) {
case BASIC -> DevOpsBasicAuthenticationDirective.devops(devOpsConfig);
case OAUTH2 -> DevOpsOAuth2AuthenticationDirective.devops(devOpsConfig, jwtAuthenticationProvider);
};
}
}
Loading
Loading