Skip to content

Commit

Permalink
[Feature/Identity] Adds Basic Auth mechanism via Internal IdP (#4798)
Browse files Browse the repository at this point in the history
[Feature/Identity] Adds Basic Auth mechanism via Internal IdP (#4798)

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com>
  • Loading branch information
DarshitChanpura authored Dec 1, 2022
1 parent 71281f1 commit acb5e4d
Show file tree
Hide file tree
Showing 33 changed files with 660 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- [Identity] Prototype Internal IdP ([#4659](https://github.com/opensearch-project/OpenSearch/pull/4659))
- [Identity] Strategy for Delegated Authority using Tokens ([#4826](https://github.com/opensearch-project/OpenSearch/pull/4826))
- [Identity] User operations: create update delete ([#4741](https://github.com/opensearch-project/OpenSearch/pull/4741))
- [Identity] Adds Basic Auth mechanism via Internal IdP ([#4798](https://github.com/opensearch-project/OpenSearch/pull/4798))

### Dependencies
- Bumps `log4j-core` from 2.18.0 to 2.19.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

package org.opensearch.index.reindex;

import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
import org.apache.lucene.util.SetOnce;
import org.opensearch.OpenSearchSecurityException;
import org.opensearch.OpenSearchStatusException;
Expand Down Expand Up @@ -82,6 +83,7 @@
import static org.opensearch.index.reindex.ReindexTestCase.matcher;
import static org.hamcrest.Matchers.containsString;

@ThreadLeakScope(ThreadLeakScope.Scope.NONE)
public class ReindexFromRemoteWithAuthTests extends OpenSearchSingleNodeTestCase {
private TransportAddress address;

Expand Down Expand Up @@ -144,6 +146,7 @@ public void testReindexSendsHeaders() throws Exception {
ReindexRequestBuilder request = new ReindexRequestBuilder(client(), ReindexAction.INSTANCE).source("source")
.destination("dest")
.setRemoteInfo(newRemoteInfo(null, null, singletonMap(TestFilter.EXAMPLE_HEADER, "doesn't matter")));

OpenSearchStatusException e = expectThrows(OpenSearchStatusException.class, () -> request.get());
assertEquals(RestStatus.BAD_REQUEST, e.status());
assertThat(e.getMessage(), containsString("Hurray! Sent the header!"));
Expand All @@ -164,7 +167,8 @@ public void testReindexWithBadAuthentication() throws Exception {
.destination("dest")
.setRemoteInfo(newRemoteInfo("junk", "auth", emptyMap()));
OpenSearchStatusException e = expectThrows(OpenSearchStatusException.class, () -> request.get());
assertThat(e.getMessage(), containsString("\"reason\":\"Bad Authorization\""));
assertThat(e.getMessage(), containsString("\"error\":\"junk does not exist in internal realm.\"")); // Due to native auth
// implementation
}

/**
Expand Down
6 changes: 5 additions & 1 deletion sandbox/libs/authn/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ dependencies {
implementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}"
implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}"

implementation 'org.apache.shiro:shiro-core:1.9.1'
api 'org.apache.shiro:shiro-core:1.9.1'
// Needed for shiro
implementation "org.slf4j:slf4j-api:${versions.slf4j}"
implementation "org.bouncycastle:bcprov-jdk15on:${versions.bouncycastle}"
Expand Down Expand Up @@ -276,3 +276,7 @@ thirdPartyAudit.ignoreMissingClasses(
'org.yaml.snakeyaml.parser.ParserImpl',
'org.yaml.snakeyaml.resolver.Resolver',
)

tasks.register("integTest", Test) {
include '**/*IT.class'
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.identity;
package org.opensearch.authn;

import org.opensearch.authn.AccessToken;
import org.opensearch.authn.tokens.AccessToken;

/**
* Vends out access tokens
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.identity;

import org.opensearch.authn.Subject;
package org.opensearch.authn;

/**
* Authentication management for OpenSearch.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.authn;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.opensearch.authn.tokens.BasicAuthToken;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
* Extracts Shiro's {@link AuthenticationToken} from different types of auth headers
*
* @opensearch.experimental
*/
public class AuthenticationTokenHandler {

private static final Logger logger = LogManager.getLogger(AuthenticationTokenHandler.class);

/**
* Extracts shiro auth token from the given header token
* @param authenticationToken the token from which to extract
* @return the extracted shiro auth token to be used to perform login
*/
public static AuthenticationToken extractShiroAuthToken(org.opensearch.authn.tokens.AuthenticationToken authenticationToken) {
AuthenticationToken authToken = null;

if (authenticationToken instanceof BasicAuthToken) {
authToken = handleBasicAuth((BasicAuthToken) authenticationToken);
}
// TODO: check for other type of HeaderTokens
return authToken;
}

/**
* Returns auth token extracted from basic auth header
* @param token the basic auth token
* @return the extracted auth token
*/
private static AuthenticationToken handleBasicAuth(final BasicAuthToken token) {

final byte[] decodedAuthHeader = Base64.getDecoder().decode(token.getHeaderValue().substring("Basic".length()).trim());
String decodedHeader = new String(decodedAuthHeader, StandardCharsets.UTF_8);

final int firstColonIndex = decodedHeader.indexOf(':');

String username = null;
String password = null;

if (firstColonIndex > 0) {
username = decodedHeader.substring(0, firstColonIndex);

if (decodedHeader.length() - 1 != firstColonIndex) {
password = decodedHeader.substring(firstColonIndex + 1);
} else {
// blank password
password = "";
}
}

if (username == null || password == null) {
logger.warn("Invalid 'Authorization' header, send 401 and 'WWW-Authenticate Basic'");
return null;
}

logger.info("Logging in as: " + username);

return new UsernamePasswordToken(username, password);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import java.io.IOException;
Expand All @@ -18,14 +20,18 @@
* @opensearch.experimental
*/
public class DefaultObjectMapper {
public static final ObjectMapper objectMapper = new ObjectMapper();
public static ObjectMapper objectMapper;
public final static ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory());
private static final ObjectMapper defaulOmittingObjectMapper = new ObjectMapper();

static {
objectMapper = JsonMapper.builder()
.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION)
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS) // to prevent access denied exception by Jackson
.build();

objectMapper.setSerializationInclusion(Include.NON_NULL);
// objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
defaulOmittingObjectMapper.setSerializationInclusion(Include.NON_DEFAULT);
defaulOmittingObjectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
YAML_MAPPER.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public enum Principals {

private final Principal principal;

private Principals(final Principal principal) {
Principals(final Principal principal) {
this.principal = principal;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

package org.opensearch.authn;

import org.opensearch.authn.tokens.AuthenticationToken;

import java.security.Principal;

/**
* An individual, process, or device that causes information to flow among objects or change to the system state.
*
* Used to authorize activities inside of the OpenSearch ecosystem.
*
* @opensearch.experimental
*/
public interface Subject {
Expand All @@ -22,7 +22,7 @@ public interface Subject {
Principal getPrincipal();

/**
* Authentications from a token
* Authentication check via the token
* throws UnsupportedAuthenticationMethod
* throws InvalidAuthenticationToken
* throws SubjectNotFound
Expand Down
10 changes: 10 additions & 0 deletions sandbox/libs/authn/src/main/java/org/opensearch/authn/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@
import java.util.Collections;
import java.util.Map;

/**
* A non-volatile and immutable object in the storage.
*
* @opensearch.experimental
*/

public class User {

@JsonProperty(value = "primary_principal")
private StringPrincipal primaryPrincipal;

@JsonProperty(value = "hash")
private String bcryptHash;

@JsonProperty(value = "attributes")
private Map<String, String> attributes = Collections.emptyMap();

@JsonProperty(value = "primary_principal")
Expand All @@ -39,10 +47,12 @@ public void setBcryptHash(String bcryptHash) {
this.bcryptHash = bcryptHash;
}

@JsonProperty(value = "attributes")
public Map<String, String> getAttributes() {
return attributes;
}

@JsonProperty(value = "attributes")
public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.opensearch.authn.internal;

import org.opensearch.authn.AccessTokenManager;
import org.opensearch.authn.tokens.AccessToken;

/**
* Implementation of access token manager that does not enforce authentication
*
* This class and related classes in this package will not return nulls or fail permissions checks
*
* @opensearch.internal
*/
public class InternalAccessTokenManager implements AccessTokenManager {

@Override
public void expireAllTokens() {
// Tokens cannot be expired
}

@Override
public AccessToken generate() {
return new AccessToken();
}

@Override
public AccessToken refresh(final AccessToken token) {
return new AccessToken();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.authn.internal;

import org.opensearch.authn.AccessTokenManager;
import org.opensearch.authn.AuthenticationManager;
import org.opensearch.authn.realm.InternalRealm;
import org.opensearch.authn.Subject;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;

/**
* Implementation of authentication manager that enforces authentication against internal idp
*
* This class and related classes in this package will not return nulls or fail permissions checks
*
* This class manages the subjects loaded via the realm, and provides current subject
* when authenticating the incoming request
* Checkout
* and how the internal Identity system uses auth manager to get current subject to use for authentication
*
* @opensearch.internal
*/
public class InternalAuthenticationManager implements AuthenticationManager {

/**
* Security manager is loaded with default user set,
* and this instantiation uses the default security manager
*/
public InternalAuthenticationManager() {
final SecurityManager securityManager = new DefaultSecurityManager(InternalRealm.INSTANCE);
SecurityUtils.setSecurityManager(securityManager);
}

/**
* Instantiates this Auth manager by setting the custom security Manager that is passed as an argument
* @param securityManager the custom security manager (with realm instantiated in it)
*/
public InternalAuthenticationManager(SecurityManager securityManager) {
SecurityUtils.setSecurityManager(securityManager);
}

@Override
public Subject getSubject() {
return new InternalSubject(SecurityUtils.getSubject());
}

@Override
public AccessTokenManager getAccessTokenManager() {
return null;
}
}
Loading

0 comments on commit acb5e4d

Please sign in to comment.