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

feat(amphora-service)!: add opa client and service #74

Merged
merged 8 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
6 changes: 6 additions & 0 deletions amphora-service/charts/amphora/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ spec:
value: {{ .Values.amphora.springActiveProfiles }}
- name: CASTOR_SERVICE_URI
value: "{{ .Values.amphora.castor.serviceUri }}"
- name: AMPHORA_USER_ID_FIELD_NAME
value: "{{ .Values.auth.userIdFieldName }}"
- name: AMPHORA_OPA_DEFAULT_POLICY_PACKAGE
value: "{{ .Values.opa.defaultPolicyPackage }}"
- name: AMPHORA_OPA_URL
value: "{{ .Values.opa.endpoint }}"
- name: MAC_KEY
value: "{{ .Values.spdz.macKey }}"
- name: SPDZ_PRIME
Expand Down
7 changes: 7 additions & 0 deletions amphora-service/charts/amphora/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,10 @@ spdz:
prime: ""
r: ""
rInv: ""

auth:
userIdFieldName: "sub"

opa:
defaultPolicyPackage: "carbynestack.def"
endpoint: "http://opa.default.svc.cluster.local:8081/"
sbckr marked this conversation as resolved.
Show resolved Hide resolved
sbckr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 - for information on the respective copyright owner
* Copyright (c) 2021-2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/amphora.
*
* SPDX-License-Identifier: Apache-2.0
Expand All @@ -13,6 +13,7 @@
import io.carbynestack.amphora.common.MaskedInput;
import io.carbynestack.amphora.common.MaskedInputData;
import io.carbynestack.amphora.common.SecretShare;
import io.carbynestack.amphora.common.Tag;
import io.carbynestack.castor.common.entities.Field;
import io.carbynestack.castor.common.entities.InputMask;
import io.carbynestack.castor.common.entities.Share;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/amphora.
*
* SPDX-License-Identifier: Apache-2.0
*/

package io.carbynestack.amphora.service.config;


import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@ConfigurationProperties(prefix = "carbynestack.auth")
@Component
@Data
@Accessors(chain = true)
public class AuthProperties {
private String userIdFieldName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/amphora.
*
* SPDX-License-Identifier: Apache-2.0
*/

package io.carbynestack.amphora.service.config;

import io.carbynestack.amphora.service.opa.JwtReader;
import io.carbynestack.amphora.service.opa.OpaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.net.URI;

@Configuration
public class OpaConfig {

@Bean
JwtReader jwtReader(AuthProperties authProperties) {
return new JwtReader(authProperties.getUserIdFieldName());
}

@Bean
OpaClient opaClient(OpaProperties opaProperties) {
return OpaClient.builder()
.opaServiceUri(URI.create(opaProperties.getEndpoint()))
.defaultPolicyPackage(opaProperties.getDefaultPolicyPackage())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/amphora.
*
* SPDX-License-Identifier: Apache-2.0
*/
package io.carbynestack.amphora.service.config;

import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@ConfigurationProperties(prefix = "carbynestack.opa")
@Component
@Data
@Accessors(chain = true)
public class OpaProperties {

private String endpoint;
private String defaultPolicyPackage;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright (c) 2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/amphora.
*
* SPDX-License-Identifier: Apache-2.0
*/

package io.carbynestack.amphora.service.exceptions;

public class CsOpaException extends Exception {
public CsOpaException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright (c) 2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/amphora.
*
* SPDX-License-Identifier: Apache-2.0
*/

package io.carbynestack.amphora.service.exceptions;

public class UnauthorizedException extends Exception {
public UnauthorizedException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/amphora.
*
* SPDX-License-Identifier: Apache-2.0
*/

package io.carbynestack.amphora.service.opa;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.carbynestack.amphora.service.exceptions.UnauthorizedException;
import io.vavr.control.Option;

import java.util.Base64;

public class JwtReader {
static ObjectMapper mapper = new ObjectMapper();
private final String userIdField;

public JwtReader(String userIdField) {
this.userIdField = userIdField;
}

public String extractUserIdFromAuthHeader(String header) throws UnauthorizedException {
return extractFieldFromAuthHeader(header, userIdField);
}

private static String extractFieldFromAuthHeader(String header, String field) throws UnauthorizedException {
return tokenFromHeader(header)
.flatMap(JwtReader::dataNodeFromToken)
.flatMap(node -> fieldFromNode(node, field))
.getOrElseThrow(() -> new UnauthorizedException("No token provided"));
}

private static Option<JsonNode> dataNodeFromToken(String token) {
String[] parts = token.split("\\.");
if (parts.length != 3) {
return Option.none();
}
try {
String jwt = new String(Base64.getDecoder().decode(parts[1]));
return Option.of(mapper.reader().readTree(jwt));
} catch (JsonProcessingException e) {
return Option.none();

Check warning on line 46 in amphora-service/src/main/java/io/carbynestack/amphora/service/opa/JwtReader.java

View check run for this annotation

Codecov / codecov/patch

amphora-service/src/main/java/io/carbynestack/amphora/service/opa/JwtReader.java#L45-L46

Added lines #L45 - L46 were not covered by tests
}
}

private static Option<String> tokenFromHeader(String header) {
if (header != null && header.startsWith("Bearer ")) {
return Option.of(header.substring(7));
}
return Option.none();
}

private static Option<String> fieldFromNode(JsonNode node, String fieldName) {
JsonNode field = node;
try {
for(String f : fieldName.split("\\.")) {
field = field.get(f);
}
return Option.of(field.asText());
} catch (NullPointerException e) {
return Option.none();
}
}
}

sbckr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/amphora.
*
* SPDX-License-Identifier: Apache-2.0
*/

package io.carbynestack.amphora.service.opa;

import io.carbynestack.amphora.common.Tag;
import io.carbynestack.httpclient.CsHttpClient;
import io.carbynestack.httpclient.CsHttpClientException;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;

import java.net.URI;
import java.util.List;

@Slf4j
public class OpaClient {
private final CsHttpClient<String> csHttpClient;
private final URI opaServiceUri;
private final String defaultPolicyPackage;

@Builder
public OpaClient(URI opaServiceUri, String defaultPolicyPackage) {
this(CsHttpClient.createDefault(), opaServiceUri, defaultPolicyPackage);
}

OpaClient(CsHttpClient<String> httpClient, URI opaServiceUri, String defaultPolicyPackage) {
this.csHttpClient = httpClient;
this.opaServiceUri = opaServiceUri;
this.defaultPolicyPackage = defaultPolicyPackage;
}

/**
* Evaluate the OPA policy package with the given action, subject and tags.
*
* @param policyPackage The OPA policy package to evaluate.
* @param action The action to evaluate.
* @param subject The subject attempting to perform the action.
* @param tags The tags describing the accessed object.
* @return True if the subject can perform the action, false otherwise (or if an error occurred).
*/
public boolean isAllowed(String policyPackage, String action, String subject, List<Tag> tags) {
OpaRequestBody body = OpaRequestBody.builder()
.subject(subject)
.tags(tags)
.build();
try {
return csHttpClient.postForObject(opaServiceUri.resolve(
String.format("/v1/data/%s/%s",
policyPackage.replace(".", "/"),
action)),
new OpaRequest(body),
OpaResult.class)
.isAllowed();
} catch (CsHttpClientException e) {
log.error("Error occurred while evaluating OPA policy package: {}", e.getMessage());
sbckr marked this conversation as resolved.
Show resolved Hide resolved
}
return false;
}

public OpaClientRequest newRequest() {
return new OpaClientRequest(this, defaultPolicyPackage);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/amphora.
*
* SPDX-License-Identifier: Apache-2.0
*/

package io.carbynestack.amphora.service.opa;

import io.carbynestack.amphora.common.Tag;
import io.carbynestack.amphora.service.exceptions.CsOpaException;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.logging.log4j.util.Strings;

import java.util.List;
import java.util.Objects;

@Setter()
@Accessors(chain = true, fluent = true)
public class OpaClientRequest {
private String withPolicyPackage;
private String withSubject;
private List<Tag> withTags;
private String withAction;

private final OpaClient opaClient;

public OpaClientRequest(OpaClient opaClient, String defaultPolicyPackage) {
this.withPolicyPackage = defaultPolicyPackage;
this.opaClient = opaClient;
}

public boolean evaluate() throws CsOpaException {
if(Strings.isEmpty(withSubject)) {
throw new CsOpaException("Subject is required to evaluate the policy");
}
if(Strings.isEmpty(withAction)) {
throw new CsOpaException("Action is required to evaluate the policy");
}
withTags.removeIf(Objects::isNull);
return opaClient.isAllowed(withPolicyPackage, withAction, withSubject, withTags);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/amphora.
*
* SPDX-License-Identifier: Apache-2.0
*/

package io.carbynestack.amphora.service.opa;

import lombok.Getter;

@Getter
class OpaRequest {
OpaRequestBody input;

public OpaRequest(OpaRequestBody input) {
this.input = input;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/amphora.
*
* SPDX-License-Identifier: Apache-2.0
*/

package io.carbynestack.amphora.service.opa;

import io.carbynestack.amphora.common.Tag;
import lombok.Builder;
import lombok.Value;

import java.util.List;

@Value
public class OpaRequestBody {
String subject;
List<Tag> tags;

@Builder
public OpaRequestBody(String subject, List<Tag> tags) {
this.subject = subject;
this.tags = tags;
}
}
Loading
Loading