forked from delta-io/delta-sharing
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Antonio Murgia <[email protected]>
- Loading branch information
1 parent
7ccfa42
commit ba9394e
Showing
7 changed files
with
281 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Authentication | ||
|
||
By default, Whitefox runs with authentication disabled. | ||
|
||
## Token authentication | ||
|
||
We provide a simple way to secure your application using a single static token. | ||
|
||
To enable token authentication you need to: | ||
|
||
- set `whitefox.server.authentication.enabled` to `true` | ||
- set `whitefox.server.authentication.bearerToken` to a secret string | ||
|
||
After doing so, you need to provide a http header such as `Authentication: Bearer $token` in every request otherwise | ||
you will receive a 401 error authentication error on the client. | ||
|
||
| property name | default value | description | | ||
|--------------------------------------------|-----------------|-------------------------------------------------| | ||
| whitefox.server.authentication.enabled | false | either true or false to enable authentication | | ||
| whitefox.server.authentication.bearerToken | | the token used for authentication | | ||
|
||
|
||
|
||
In order to set configurations refer to [Quarkus documentation](https://quarkus.io/guides/config-reference). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
server/app/src/main/java/io/whitefox/api/server/auth/SimpleTokenAuthenticationMechanism.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package io.whitefox.api.server.auth; | ||
|
||
import io.netty.handler.codec.http.HttpHeaderNames; | ||
import io.netty.handler.codec.http.HttpResponseStatus; | ||
import io.quarkus.security.AuthenticationFailedException; | ||
import io.quarkus.security.identity.IdentityProviderManager; | ||
import io.quarkus.security.identity.SecurityIdentity; | ||
import io.quarkus.security.identity.request.AnonymousAuthenticationRequest; | ||
import io.quarkus.security.identity.request.AuthenticationRequest; | ||
import io.quarkus.security.runtime.QuarkusPrincipal; | ||
import io.quarkus.security.runtime.QuarkusSecurityIdentity; | ||
import io.quarkus.vertx.http.runtime.security.ChallengeData; | ||
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism; | ||
import io.smallrye.mutiny.Uni; | ||
import io.vertx.ext.web.RoutingContext; | ||
import jakarta.inject.Inject; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
|
||
public class SimpleTokenAuthenticationMechanism implements HttpAuthenticationMechanism { | ||
|
||
private static final String AUTHORIZATION_HEADER = "Authorization"; | ||
private static final QuarkusPrincipal principal = new QuarkusPrincipal("Mr. WhiteFox"); | ||
|
||
String token; | ||
|
||
@Inject | ||
IdentityProviderManager identityProvider; | ||
|
||
public SimpleTokenAuthenticationMechanism(String token) { | ||
this.token = token; | ||
} | ||
|
||
private Uni<SecurityIdentity> anonymous() { | ||
return identityProvider.authenticate(AnonymousAuthenticationRequest.INSTANCE); | ||
} | ||
|
||
@Override | ||
public Uni<SecurityIdentity> authenticate( | ||
RoutingContext context, IdentityProviderManager identityProviderManager) { | ||
if (context.normalizedPath().startsWith("/q/")) return anonymous(); | ||
var optionalHeader = Optional.ofNullable(context.request().headers().get(AUTHORIZATION_HEADER)); | ||
QuarkusSecurityIdentity identity = | ||
new QuarkusSecurityIdentity.Builder().setPrincipal(principal).build(); | ||
AuthenticationFailedException missingOrUnrecognizedCredentials = | ||
new AuthenticationFailedException("Missing or unrecognized credentials"); | ||
AuthenticationFailedException missingToken = new AuthenticationFailedException( | ||
"Simple authentication enabled, but token is missing in the request"); | ||
if (optionalHeader.isEmpty()) throw missingToken; | ||
else { | ||
if (Objects.equals("Bearer " + token, optionalHeader.get())) { | ||
return Uni.createFrom().item(identity); | ||
} else { | ||
throw missingOrUnrecognizedCredentials; | ||
} | ||
} | ||
} | ||
|
||
// Not really needed for this mechanism, does not get called; | ||
// The response to the user is handled through a custom attemptAuthentication from | ||
// WhitefoxHttpAuthenticator | ||
@Override | ||
public Uni<ChallengeData> getChallenge(RoutingContext context) { | ||
var challenge = "token -> " + token; | ||
ChallengeData result = new ChallengeData( | ||
HttpResponseStatus.UNAUTHORIZED.code(), HttpHeaderNames.WWW_AUTHENTICATE, challenge); | ||
return Uni.createFrom().item(result); | ||
} | ||
|
||
// no need for this mechanism, for others | ||
@Override | ||
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() { | ||
return null; | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
server/app/src/main/java/io/whitefox/api/server/auth/WhitefoxAuthenticationConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package io.whitefox.api.server.auth; | ||
|
||
import io.smallrye.config.ConfigMapping; | ||
import io.smallrye.config.WithDefault; | ||
import io.smallrye.config.WithName; | ||
import java.util.Optional; | ||
|
||
/** Configuration for Whitefox authentication settings. */ | ||
@ConfigMapping(prefix = "whitefox.server.authentication") | ||
public interface WhitefoxAuthenticationConfig { | ||
|
||
/** Returns {@code true} if Whitefox authentication is enabled. */ | ||
@WithName("enabled") | ||
@WithDefault("false") | ||
boolean enabled(); | ||
|
||
/** Bearer token that should be used in requests to grant authorization. */ | ||
@WithName("bearerToken") | ||
Optional<String> bearerToken(); | ||
} |
75 changes: 75 additions & 0 deletions
75
server/app/src/main/java/io/whitefox/api/server/auth/WhitefoxHttpAuthenticator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package io.whitefox.api.server.auth; | ||
|
||
import io.quarkus.security.AuthenticationFailedException; | ||
import io.quarkus.security.identity.IdentityProvider; | ||
import io.quarkus.security.identity.IdentityProviderManager; | ||
import io.quarkus.security.identity.SecurityIdentity; | ||
import io.quarkus.security.identity.request.AnonymousAuthenticationRequest; | ||
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism; | ||
import io.quarkus.vertx.http.runtime.security.HttpAuthenticator; | ||
import io.quarkus.vertx.http.runtime.security.PathMatchingHttpSecurityPolicy; | ||
import io.smallrye.mutiny.Uni; | ||
import io.vertx.ext.web.RoutingContext; | ||
import jakarta.annotation.Priority; | ||
import jakarta.enterprise.inject.Alternative; | ||
import jakarta.enterprise.inject.Instance; | ||
import jakarta.inject.Inject; | ||
import jakarta.inject.Singleton; | ||
|
||
/** | ||
* A custom {@link HttpAuthenticator}. This authenticator that performs the following main duties: | ||
* | ||
* <ul> | ||
* <li>Authenticates requests using a token provided in the application.properties when authentication is enabled. | ||
* <li>Completely disallows unauthenticated requests when authentication is enabled. | ||
* </ul> | ||
*/ | ||
@Alternative // @Alternative + @Priority ensure the original HttpAuthenticator bean is not used | ||
@Priority(1) | ||
@Singleton | ||
public class WhitefoxHttpAuthenticator extends HttpAuthenticator { | ||
|
||
private final IdentityProviderManager identityProvider; | ||
private final boolean authEnabled; | ||
private final WhitefoxAuthenticationConfig config; | ||
|
||
@Inject | ||
public WhitefoxHttpAuthenticator( | ||
WhitefoxAuthenticationConfig config, | ||
IdentityProviderManager identityProviderManager, | ||
Instance<PathMatchingHttpSecurityPolicy> pathMatchingPolicy, | ||
Instance<HttpAuthenticationMechanism> httpAuthenticationMechanism, | ||
Instance<IdentityProvider<?>> providers) { | ||
super(identityProviderManager, pathMatchingPolicy, httpAuthenticationMechanism, providers); | ||
this.identityProvider = identityProviderManager; | ||
this.config = config; | ||
authEnabled = config.enabled(); | ||
} | ||
|
||
private HttpAuthenticationMechanism selectAuthenticationMechanism( | ||
WhitefoxAuthenticationConfig config, RoutingContext context) { | ||
if (config.bearerToken().isPresent()) { | ||
return new SimpleTokenAuthenticationMechanism(config.bearerToken().get()); | ||
} else { | ||
throw new AuthenticationFailedException( | ||
"Other auth mechanisms not supported right now! Please add your token to application.properties"); | ||
} | ||
} | ||
|
||
@Override | ||
public Uni<SecurityIdentity> attemptAuthentication(RoutingContext context) { | ||
if (!authEnabled) { | ||
return anonymous(); | ||
} | ||
// quarkus dev paths | ||
else if (context.normalizedPath().startsWith("/q/")) { | ||
return anonymous(); | ||
} else { | ||
return selectAuthenticationMechanism(config, context).authenticate(context, identityProvider); | ||
} | ||
} | ||
|
||
private Uni<SecurityIdentity> anonymous() { | ||
return identityProvider.authenticate(AnonymousAuthenticationRequest.INSTANCE); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
io.delta.sharing.api.server.defaultMaxResults=10 | ||
quarkus.banner.path=banner.txt | ||
quarkus.banner.path=banner.txt | ||
|
||
whitefox.server.authentication.enabled=false |
82 changes: 82 additions & 0 deletions
82
server/app/src/test/java/io/whitefox/api/server/auth/WhitefoxHttpAuthenticatorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package io.whitefox.api.server.auth; | ||
|
||
import static io.restassured.RestAssured.given; | ||
|
||
import io.quarkus.test.junit.QuarkusTest; | ||
import io.quarkus.test.junit.QuarkusTestProfile; | ||
import io.quarkus.test.junit.TestProfile; | ||
import io.restassured.http.Header; | ||
import io.whitefox.api.model.v1.generated.CreateMetastore; | ||
import io.whitefox.api.model.v1.generated.MetastoreProperties; | ||
import io.whitefox.api.model.v1.generated.SimpleAwsCredentials; | ||
import java.util.Map; | ||
import org.junit.jupiter.api.Nested; | ||
import org.junit.jupiter.api.Test; | ||
|
||
@QuarkusTest | ||
@TestProfile(WhitefoxHttpAuthenticatorTest.AuthenticationProfile.class) | ||
public class WhitefoxHttpAuthenticatorTest { | ||
|
||
private final CreateMetastore createMetastore = new CreateMetastore() | ||
.name("glue_metastore_prod") | ||
.skipValidation(false) | ||
.type(CreateMetastore.TypeEnum.GLUE) | ||
.properties(new MetastoreProperties() | ||
.catalogId("123") | ||
.credentials(new SimpleAwsCredentials() | ||
.awsAccessKeyId("access") | ||
.awsSecretAccessKey("secret") | ||
.region("eu-west1"))); | ||
|
||
public static class AuthenticationProfile implements QuarkusTestProfile { | ||
@Override | ||
public Map<String, String> getConfigOverrides() { | ||
return Map.of( | ||
"whitefox.server.authentication.enabled", | ||
"true", | ||
"whitefox.server.authentication.bearerToken", | ||
"myToken"); | ||
} | ||
} | ||
|
||
public static class NoAuthenticationProfile implements QuarkusTestProfile { | ||
@Override | ||
public Map<String, String> getConfigOverrides() { | ||
return Map.of("whitefox.server.authentication.enabled", "false"); | ||
} | ||
} | ||
|
||
// Nesting two test-profiles leads to an OOM: https://github.com/quarkusio/quarkus/issues/12498 | ||
@Test | ||
void expectDenied() { | ||
given() | ||
.when() | ||
.get("/whitefox-api/v1/metastores/{name}", createMetastore.getName()) | ||
.then() | ||
.statusCode(401); | ||
} | ||
|
||
@Test | ||
void expectAcceptedWithAuth() { | ||
given() | ||
.when() | ||
.header(new Header("Authorization", "Bearer myToken")) | ||
.get("/whitefox-api/v1/metastores/{name}", createMetastore.getName()) | ||
.then() | ||
.statusCode(404); | ||
} | ||
|
||
@Nested | ||
@TestProfile(WhitefoxHttpAuthenticatorTest.NoAuthenticationProfile.class) | ||
class TestNotAuthorized { | ||
|
||
@Test | ||
void expectAccepted() { | ||
given() | ||
.when() | ||
.get("/whitefox-api/v1/metastores/{name}", createMetastore.getName()) | ||
.then() | ||
.statusCode(404); | ||
} | ||
} | ||
} |