Commit a0440750 authored by Rustam Lotsmanenko (EPAM)'s avatar Rustam Lotsmanenko (EPAM)
Browse files

Merge branch 'gcp-partition-admin-accounts' into 'master'

Configurable partition admin accounts (GONRG-2687)

See merge request !78
parents 4878385c 8b46758a
Pipeline #55930 failed with stages
in 31 minutes and 19 seconds
......@@ -19,6 +19,11 @@ variables:
MAVEN_PROJECTS: "-pl partition-core,provider/partition-gcp"
OSDU_GCP_TEST_SUBDIR: testing/$OSDU_GCP_SERVICE-test-$OSDU_GCP_VENDOR
OSDU_GCP_HELM_PACKAGE_CHARTS: "devops/gcp/deploy devops/gcp/configmap"
OSDU_GCP_HELM_NAMESPACE: default
OSDU_GCP_HELM_CONFIG_SERVICE_VARS: "--set data.partition_admin_accounts=$OSDU_GCP_PARTITION_ADMIN_ACCOUNTS --set data.google_cloud_project=$OSDU_GCP_PROJECT --set data.google_audiences=$GOOGLE_AUDIENCE --set data.log_level=INFO --set data.key_ring=$OSDU_GCP_PARTITION_KEY_RING --set data.kms_key=$OSDU_GCP_PARTITION_KMS_KEY"
OSDU_GCP_HELM_DEPLOYMENT_SERVICE_VARS: "--set data.image=$CI_REGISTRY_IMAGE/osdu-gcp:$CI_COMMIT_SHORT_SHA --set data.serviceAccountName=workload-identity-partition"
OSDU_GCP_HELM_CONFIG_SERVICE: partition-config
OSDU_GCP_HELM_DEPLOYMENT_SERVICE: partition-deploy
include:
- project: "osdu/platform/ci-cd-pipelines"
......@@ -43,7 +48,10 @@ include:
file: "cloud-providers/ibm.yml"
- project: "osdu/platform/ci-cd-pipelines"
file: "cloud-providers/osdu-gcp-cloudrun.yml"
file: "cloud-providers/osdu-gcp-gke.yml"
- project: "osdu/platform/ci-cd-pipelines"
file: 'cloud-providers/osdu-gcp-global.yml'
- project: "osdu/platform/ci-cd-pipelines"
file: "publishing/pages.yml"
......
This diff is collapsed.
......@@ -7,7 +7,7 @@ metadata:
namespace: "{{ .Release.Namespace }}"
data:
GOOGLE_CLOUD_PROJECT: "{{ .Values.data.google_cloud_project }}"
PARTITION_ADMIN_ACCOUNT: "{{ .Values.data.partition_admin_account }}"
PARTITION_ADMIN_ACCOUNTS: "{{ .Values.data.partition_admin_accounts }}"
GOOGLE_AUDIENCES: "{{ .Values.data.google_audiences }}"
KEY_RING: "{{ .Values.data.key_ring }}"
KMS_KEY: "{{ .Values.data.kms_key }}"
......
data:
google_cloud_project: ""
partition_admin_account: ""
partition_admin_accounts: ""
google_audiences: ""
key_ring: ""
kms_key: ""
......
......@@ -22,7 +22,7 @@ In order to run the service locally or remotely, you will need to have the follo
| `AUTHORIZE_API` | ex `https://entitlements.com/entitlements/v1` | Entitlements API endpoint | no | output of infrastructure deployment |
| `GOOGLE_CLOUD_PROJECT` | ex `osdu-cicd-epam` | Google Cloud Project Id| no | output of infrastructure deployment |
| `GOOGLE_AUDIENCES` | ex `*****.apps.googleusercontent.com` | Client ID for getting access to cloud resources | yes | https://console.cloud.google.com/apis/credentials |
| `PARTITION_ADMIN_ACCOUNT` | ex `admin@domen.iam.gserviceaccount.com` | Partition Admin account email | no | - |
| `PARTITION_ADMIN_ACCOUNTS` | ex `admin@domen.iam.gserviceaccount.com,osdu-gcp-sa,workload-identity` | List of partition admin account emails, could be in full form like `admin@domen.iam.gserviceaccount.com` or in `starts with` pattern like `osdu-gcp-sa`| no | - |
| `GOOGLE_APPLICATION_CREDENTIALS` | ex `/path/to/directory/service-key.json` | Service account credentials, you only need this if running locally | yes | https://console.cloud.google.com/iam-admin/serviceaccounts |
| `KEY_RING` | ex `csqp` | A key ring holds keys in a specific Google Cloud location and permit us to manage access control on groups of keys | yes | https://cloud.google.com/kms/docs/resource-hierarchy#key_rings |
| `KMS_KEY` | ex `partitionService` | A key exists on one key ring linked to a specific location. | yes | https://cloud.google.com/kms/docs/resource-hierarchy#key_rings |
......
/*
* Copyright 2021 Google LLC
* Copyright 2021 EPAM Systems, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opengroup.osdu.partition.provider.gcp.config;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RequiredArgsConstructor
public class IDTokenVerifierConfiguration {
private final PropertiesConfiguration configuration;
@Bean
public GoogleIdTokenVerifier buildTokenVerifier() throws GeneralSecurityException, IOException {
return new GoogleIdTokenVerifier.Builder(
GoogleNetHttpTransport.newTrustedTransport(),
JacksonFactory.getDefaultInstance())
.setAudience(Collections.singleton(configuration.getGoogleAudiences()))
.build();
}
}
......@@ -17,6 +17,9 @@
package org.opengroup.osdu.partition.provider.gcp.config;
import java.util.List;
import java.util.Objects;
import javax.annotation.PostConstruct;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
......@@ -30,9 +33,21 @@ public class PropertiesConfiguration {
private String googleAudiences;
private String partitionAdminAccount;
private List<String> partitionAdminAccounts;
private String googleCloudProject;
private int cacheExpiration;
private int cacheMaxSize;
private String serviceAccountTail;
@PostConstruct
public void setUp() {
if (Objects.isNull(serviceAccountTail) || serviceAccountTail.isEmpty()) {
this.serviceAccountTail = googleCloudProject + ".iam.gserviceaccount.com";
}
}
}
......@@ -19,9 +19,7 @@ package org.opengroup.osdu.partition.provider.gcp.security;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
......@@ -43,6 +41,8 @@ public class AuthorizationService implements IAuthorizationService {
private final DpsHeaders headers;
private final GoogleIdTokenVerifier verifier;
@Override
public boolean isDomainAdminServiceAccount() {
if (Objects.isNull(headers.getAuthorization()) || headers.getAuthorization().isEmpty()) {
......@@ -50,13 +50,6 @@ public class AuthorizationService implements IAuthorizationService {
}
String email = null;
try {
GoogleIdTokenVerifier verifier =
new GoogleIdTokenVerifier.Builder(
GoogleNetHttpTransport.newTrustedTransport(),
JacksonFactory.getDefaultInstance())
.setAudience(Collections.singleton(configuration.getGoogleAudiences()))
.build();
String authorization = headers.getAuthorization().replace("Bearer ", "");
GoogleIdToken googleIdToken = verifier.verify(authorization);
if (Objects.isNull(googleIdToken)) {
......@@ -64,25 +57,37 @@ public class AuthorizationService implements IAuthorizationService {
throw AppException.createUnauthorized("Unauthorized. The JWT token could not be validated");
}
email = googleIdToken.getPayload().getEmail();
String partitionAdminAccount = configuration.getPartitionAdminAccount();
if (Objects.nonNull(partitionAdminAccount) && !partitionAdminAccount.isEmpty()) {
if (email.equals(partitionAdminAccount)) {
return true;
} else {
throw AppException
.createUnauthorized(String.format("Unauthorized. The user %s is untrusted.", email));
}
List<String> partitionAdminAccounts = configuration.getPartitionAdminAccounts();
if (Objects.nonNull(partitionAdminAccounts) && !partitionAdminAccounts.isEmpty()) {
return isAllowedAccount(email);
} else {
if (StringUtils.endsWithIgnoreCase(email, "gserviceaccount.com")) {
if (StringUtils.endsWith(email, configuration.getServiceAccountTail())) {
return true;
} else {
throw AppException.createUnauthorized(
String.format("Unauthorized. The user %s is not Service Principal", email));
}
}
} catch (AppException e){
throw e;
} catch (Exception ex) {
log.warn(String.format("User %s is not unauthorized. %s.", email, ex));
throw AppException.createUnauthorized("Unauthorized. The JWT token could not be validated");
}
}
private boolean isAllowedAccount(String accountEmail) {
if (StringUtils.endsWith(accountEmail, configuration.getServiceAccountTail())) {
for (String partitionAdmin : configuration.getPartitionAdminAccounts()) {
if (partitionAdmin.equals(accountEmail)) {
return true;
}
if (StringUtils.startsWith(accountEmail, partitionAdmin)) {
return true;
}
}
}
throw AppException
.createUnauthorized(String.format("Unauthorized. The user %s is untrusted.", accountEmail));
}
}
......@@ -18,7 +18,7 @@ KEY_RING=${key-ring}
KMS_KEY=${kms-key}
GOOGLE_CLOUD_PROJECT=${google-cloud-project}
google-audiences=123.apps.googleusercontent.com
partition-admin-account=admin@domen.iam.gserviceaccount.com
partition-admin-accounts=osdu-gcp-sa
#logging configuration
logging.level.org.springframework.web=${LOG_LEVEL:DEBUG}
......
/*
* Copyright 2021 Google LLC
* Copyright 2021 EPAM Systems, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opengroup.osdu.partition.provider.gcp.security;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.List;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.FromDataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.opengroup.osdu.core.common.model.http.AppException;
import org.opengroup.osdu.core.common.model.http.DpsHeaders;
import org.opengroup.osdu.partition.provider.gcp.config.PropertiesConfiguration;
@RunWith(Theories.class)
public class AuthorizationServiceTest {
private final String token = "abc";
private final String serviceAccountTail = "project-id.iam.gserviceaccount.com";
private final List<String> partitionAdminAccounts = ImmutableList.of("osdu-gcp-sa", "service.account@project-id.iam.gserviceaccount.com");
@Rule
public ExpectedException exceptionRule = ExpectedException.none();
@DataPoints("VALID_ACCOUNTS")
public static List<String> validTestSet() {
return ImmutableList.of(
"osdu-gcp-sa-first@project-id.iam.gserviceaccount.com",
"osdu-gcp-sa-second@project-id.iam.gserviceaccount.com",
"osdu-gcp-sa-third@project-id.iam.gserviceaccount.com",
"osdu-gcp-sa-fourth@project-id.iam.gserviceaccount.com");
}
@DataPoints("NOT_VALID_ACCOUNTS")
public static List<String> notValidTestSet() {
return ImmutableList.of(
"osdu-gcp-sa-first@google.com",
"osdu-gcp-sa-second@project-id.iam.gserviceaccount.com.not.valid",
"user-osdu-gcp-sa-third@project-id.iam.gserviceaccount.com");
}
@Mock
private PropertiesConfiguration configuration;
@Mock
private GoogleIdTokenVerifier verifier;
@Mock
private GoogleIdToken googleIdToken;
@Mock
private DpsHeaders headers;
private Payload payload = new Payload();
@InjectMocks
private AuthorizationService authorizationService;
@Before
public void setUp() throws GeneralSecurityException, IOException {
MockitoAnnotations.initMocks(this);
when(configuration.getPartitionAdminAccounts()).thenReturn(partitionAdminAccounts);
when(configuration.getServiceAccountTail()).thenReturn(serviceAccountTail);
when(headers.getAuthorization()).thenReturn(token);
when(verifier.verify(token)).thenReturn(googleIdToken);
when(googleIdToken.getPayload()).thenReturn(payload);
}
@Test
public void testProvidedInConfigAdminAccountShouldReturnTrue() {
payload.setEmail("service.account@project-id.iam.gserviceaccount.com");
assertTrue(authorizationService.isDomainAdminServiceAccount());
}
@Test(expected = AppException.class)
public void testNotProvidedInConfigAdminAccountShouldThrowException() {
payload.setEmail("user@google.com");
authorizationService.isDomainAdminServiceAccount();
}
@Theory
public void testProvidedInConfigPatternShouldReturnTrue(@FromDataPoints("VALID_ACCOUNTS") String account) {
payload.setEmail(account);
assertTrue(authorizationService.isDomainAdminServiceAccount());
}
@Theory
public void testNotProvidedInConfigPatternShouldReturnTrue(@FromDataPoints("NOT_VALID_ACCOUNTS") String account) {
exceptionRule.expect(AppException.class);
payload.setEmail(account);
authorizationService.isDomainAdminServiceAccount();
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment