From b2f02ebfe3b9eef79b8e80ea12e2184c82a96cf5 Mon Sep 17 00:00:00 2001
From: Ethiraj Krishnamanaidu <EKrishnamanaidu@slb.com>
Date: Sat, 18 Apr 2020 15:34:18 -0500
Subject: [PATCH] ado-code merge and removed SLB reference

---
 .gitignore                                    |   6 +
 .gitlab-ci.yml                                |   4 +-
 .../opengroup/osdu/legal/api/LegalTagApi.java |   4 +
 provider/legal-aws/.env.template              |  63 +++
 .../Automated/config-bucket.yml               |   6 +-
 .../CloudFormation/Automated/ecs-cluster.yml  | 212 ++++++-
 .../CloudFormation/Automated/ecs-network.yml  |  98 ++--
 .../CloudFormation/Automated/sns-topic.yml    |  22 +-
 .../JarDeploy/CodePipeline-JarDeploy.yml      | 252 +++++++++
 .../Manual/01-CreateCodePipeline.yml          | 363 +-----------
 .../CloudFormation/Master/os-legal-master.yml |  85 ++-
 .../Params/dev.template_configuration.json    |  10 +-
 .../Params/prod.template_configuration.json   |  10 +-
 .../Params/uat.template_configuration.json    |  10 +-
 provider/legal-aws/Dockerfile                 |  11 +-
 provider/legal-aws/buildspec-jar-deploy.yml   |  64 +++
 provider/legal-aws/buildspec-post-deploy.yml  |  20 +-
 provider/legal-aws/buildspec-pre-deploy.yml   |   7 +-
 provider/legal-aws/pom.xml                    |  70 ++-
 .../dataaccess/LegalTagRepositoryImpl.java    |   2 +-
 .../src/main/resources/application.properties |   8 +-
 .../aws/api/LegalTagRepositoryImplTest.java   |   2 +-
 provider/legal-ibm/README.md                  |  43 ++
 provider/legal-ibm/pom.xml                    | 107 ++++
 .../osdu/legal/ibm/LegalApplication.java      |  29 +
 .../countries/StorageReaderFactoryImpl.java   | 110 ++++
 .../ibm/countries/StorageReaderImpl.java      | 111 ++++
 .../osdu/legal/ibm/di/DevNullPublisher.java   |  46 ++
 .../osdu/legal/ibm/di/DpsLogFactory.java      |  41 ++
 .../legal/ibm/jobs/LegalTagPublisherImpl.java |  65 +++
 .../legal/ibm/security/SecurityConfig.java    |  24 +
 .../legal/ibm/security/WhoamiController.java  |  25 +
 .../ibm/tags/CloudantBackedLegalTag.java      | 100 ++++
 .../ibm/tags/CloudantLegalTagRepository.java  | 350 ++++++++++++
 .../src/main/resources/application.yml        |  35 ++
 .../src/main/resources/logback.groovy         |  43 ++
 .../ibm/api/EntitlementsFactoryByoc.java      |  31 ++
 .../ibm/api/EntitlementsServiceByoc.java      |  93 ++++
 .../osdu/legal/ibm/api/LegalTagApiTest.java   |  85 +++
 .../osdu/legal/ibm/tags/LegalTagTests.java    | 519 ++++++++++++++++++
 .../src/test/resources/logback.groovy         |  40 ++
 testing/legal-test-aws/pom.xml                |  10 +-
 .../osdu/legal/util/AwsLegalTagUtils.java     |   2 +-
 .../osdu/legal/util/LegalTagUtils.java        |   2 +-
 .../opengroup/osdu/legal/util/TestUtils.java  |   3 -
 testing/legal-test-ibm/pom.xml                |  93 ++++
 testing/legal-test-ibm/run_service.sh         |  53 ++
 testing/legal-test-ibm/run_tests.sh           |  20 +
 testing/legal-test-ibm/setup_acceptance.sh    |  52 ++
 .../TestCreateLegalTagApiAcceptance.java      | 124 +++++
 .../TestDeleteLegalTagApiAcceptance.java      |  46 ++
 .../TestGetLegalTagApiAcceptance.java         |  40 ++
 ...estGetLegalTagPropertiesApiAcceptance.java |  40 ++
 .../TestGetLegalTagsApiAcceptance.java        |  43 ++
 .../TestListLegalTagsApiAcceptance.java       |  43 ++
 .../TestUpdateLegalTagApiAcceptance.java      |  43 ++
 .../TestValidateLegalTagsApiAcceptance.java   |  42 ++
 .../util/IBMLegalTagUtils.java                |  21 +
 .../src/test/resources/InitialTags.json       |  76 +++
 .../src/test/resources/Tenant.json            |  17 +
 testing/legal-test-ibm/teardown_acceptance.sh |  10 +
 61 files changed, 3537 insertions(+), 469 deletions(-)
 create mode 100644 provider/legal-aws/.env.template
 create mode 100644 provider/legal-aws/CloudFormation/JarDeploy/CodePipeline-JarDeploy.yml
 create mode 100644 provider/legal-aws/buildspec-jar-deploy.yml
 create mode 100644 provider/legal-ibm/README.md
 create mode 100644 provider/legal-ibm/pom.xml
 create mode 100644 provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/LegalApplication.java
 create mode 100644 provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/countries/StorageReaderFactoryImpl.java
 create mode 100644 provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/countries/StorageReaderImpl.java
 create mode 100644 provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/di/DevNullPublisher.java
 create mode 100644 provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/di/DpsLogFactory.java
 create mode 100644 provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/jobs/LegalTagPublisherImpl.java
 create mode 100644 provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/security/SecurityConfig.java
 create mode 100644 provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/security/WhoamiController.java
 create mode 100644 provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/tags/CloudantBackedLegalTag.java
 create mode 100644 provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/tags/CloudantLegalTagRepository.java
 create mode 100644 provider/legal-ibm/src/main/resources/application.yml
 create mode 100644 provider/legal-ibm/src/main/resources/logback.groovy
 create mode 100644 provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/EntitlementsFactoryByoc.java
 create mode 100644 provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/EntitlementsServiceByoc.java
 create mode 100644 provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/LegalTagApiTest.java
 create mode 100644 provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/tags/LegalTagTests.java
 create mode 100644 provider/legal-ibm/src/test/resources/logback.groovy
 create mode 100644 testing/legal-test-ibm/pom.xml
 create mode 100644 testing/legal-test-ibm/run_service.sh
 create mode 100644 testing/legal-test-ibm/run_tests.sh
 create mode 100644 testing/legal-test-ibm/setup_acceptance.sh
 create mode 100644 testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestCreateLegalTagApiAcceptance.java
 create mode 100644 testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestDeleteLegalTagApiAcceptance.java
 create mode 100644 testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagApiAcceptance.java
 create mode 100644 testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagPropertiesApiAcceptance.java
 create mode 100644 testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagsApiAcceptance.java
 create mode 100644 testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestListLegalTagsApiAcceptance.java
 create mode 100644 testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestUpdateLegalTagApiAcceptance.java
 create mode 100644 testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestValidateLegalTagsApiAcceptance.java
 create mode 100644 testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/util/IBMLegalTagUtils.java
 create mode 100644 testing/legal-test-ibm/src/test/resources/InitialTags.json
 create mode 100644 testing/legal-test-ibm/src/test/resources/Tenant.json
 create mode 100644 testing/legal-test-ibm/teardown_acceptance.sh

diff --git a/.gitignore b/.gitignore
index 8ea620568..b7efba2c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,8 +27,14 @@ target/
 /.nb-gradle/
 build/
 
+/provider/*/bin
+/legal-core/bin
+
 ### VS Code ###
 .vscode/
 
 ### macOS ###
 *.DS_Store
+
+# Environment configuration
+*.env
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c8e06e7e9..12a86fc19 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,13 +4,13 @@ variables:
   AWS_ENVIRONMENT: dev
   GCP_BUILD_SUBDIR: provider/legal-gcp
   GCP_INT_TEST_SUBDIR: testing/legal-test-gcp
-  GCP_APPLICATION_NAME: os-legal
+  GCP_APPLICATION_NAME: osdu-legal
   GCP_ENVIRONMENT: dev
   GCP_PROJECT: opendes
   GCP_TENANT_NAME: opendes
   GCP_DEPLOY_ENV: p4d
   GCP_DOMAIN: cloud.slb-ds.com
-  GCP_STORAGE_URL: https://os-legal-dot-opendes.appspot.com/api/legal/v2/
+  GCP_STORAGE_URL: https://osdu-legal-dot-opendes.appspot.com/api/legal/v2/
 
 include:
   - project: 'osdu/platform/ci-cd-pipelines'
diff --git a/legal-core/src/main/java/org/opengroup/osdu/legal/api/LegalTagApi.java b/legal-core/src/main/java/org/opengroup/osdu/legal/api/LegalTagApi.java
index 21db1b3f6..af147a96c 100644
--- a/legal-core/src/main/java/org/opengroup/osdu/legal/api/LegalTagApi.java
+++ b/legal-core/src/main/java/org/opengroup/osdu/legal/api/LegalTagApi.java
@@ -10,6 +10,7 @@ import org.opengroup.osdu.core.common.model.http.RequestInfo;
 import org.opengroup.osdu.core.common.model.legal.ServiceConfig;
 
 import javax.validation.Valid;
+import javax.validation.ValidationException;
 import javax.validation.constraints.NotNull;
 import javax.inject.Inject;
 
@@ -86,6 +87,9 @@ public class LegalTagApi {
     @PreAuthorize("@authorizationFilter.hasPermission('" + ServiceConfig.LEGAL_USER + "', '" + ServiceConfig.LEGAL_EDITOR + "', '" + ServiceConfig.LEGAL_ADMIN + "')")
     @GetMapping("/legaltags")
     public ResponseEntity<LegalTagDtos> listLegalTags(@RequestParam(name = "valid", required = false, defaultValue = "true") boolean valid) {
+    	if (requestInfo.getTenantInfo() == null) {
+    		throw new ValidationException("No tenant supplied");
+    	}
         LegalTagDtos output = legalTagService.list(valid, requestInfo.getTenantInfo().getName());
         return new ResponseEntity<LegalTagDtos>(output, HttpStatus.OK);
     }
diff --git a/provider/legal-aws/.env.template b/provider/legal-aws/.env.template
new file mode 100644
index 000000000..380262362
--- /dev/null
+++ b/provider/legal-aws/.env.template
@@ -0,0 +1,63 @@
+# Copyright © Amazon Web Services
+#
+# 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
+#
+#      http://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.
+
+##### Sample os-legal .env file ###########################################################
+#
+# Basic use: duplicate this file, and make sure the new copy is also in the root of the AWS
+# 'provider' folder, and name it `.env`. Note that on macOS, by default, files starting with
+# are considered hidden system files, and are not displayed by default in Finder or the file
+# selector (which you will need to use when adding the environment file(s) to the run
+# configuration(s). While you can change a setting to show hidden files and folders by
+# default, there is also a keyboard shortcut to quickly toggle between hide/show. With either
+# Finder as the active application ("Finder" appears next to the Apple logo in the Menu Bar),
+# press: command + shift + . (period). You can store configurations for multiple environments
+# by adding more duplicates following a naming scheme of your choosing, for example:
+# `staging.env`, `uat.env`, or `local.env`.
+#
+# This requires installing a plugin to your IDE that allows you to use a .env
+# file in your repository folder (does NOT get checked into source control;
+# only the sample environment configuration (sample.env) should be committed.
+#
+# Download links for .env file plugins:
+# IntelliJ - https://github.com/Ashald/EnvFile
+
+##### Authentication / Secrets #####
+# Replace placeholder text with your own AWS secret access keys
+# and rename to `.env` - do NOT check-in .env with your credentials! Leave it in .gitignore
+AWS_ACCESS_KEY_ID=
+AWS_SECRET_KEY=
+AWS_ACCOUNT_ID=
+
+##### URLs/Ports - these values are most likely to change between environments #############
+APPLICATION_PORT=
+DOMAIN=
+
+
+##### Other environment variables ##########################################################
+JAVA_HEAP_MEMORY=
+SNS_TOPIC_NAME=
+S3_LEGAL_CONFIG_BUCKET=
+ENVIRONMENT=
+AWS_REGION=
+
+##### Integration test-specific - these are only used for integration tests, not the app ###
+AWS_COGNITO_AUTH_FLOW=
+AWS_COGNITO_AUTH_PARAMS_PASSWORD=
+AWS_COGNITO_AUTH_PARAMS_USER=
+AWS_COGNITO_AUTH_PARAMS_USER_NO_ACCESS=
+AWS_COGNITO_CLIENT_ID=
+HOST_URL=
+MY_TENANT=
+AWS_S3_ENDPOINT=
+AWS_S3_REGION=
\ No newline at end of file
diff --git a/provider/legal-aws/CloudFormation/Automated/config-bucket.yml b/provider/legal-aws/CloudFormation/Automated/config-bucket.yml
index 8f1bfd9e2..442fc4c53 100644
--- a/provider/legal-aws/CloudFormation/Automated/config-bucket.yml
+++ b/provider/legal-aws/CloudFormation/Automated/config-bucket.yml
@@ -34,7 +34,7 @@ Parameters:
     Default: us-east-1
 
   LegalConfigBucketName:
-    Description: The name of the legal service config S3 bucket. Defaults to osdu-legal-config.
+    Description: The base name of the legal service config S3 bucket. Will be prefixed by the environment and account ID.
     AllowedPattern: "^[a-zA-Z]+[0-9a-zA-Z_-]*$"
     ConstraintDescription: Must start with a letter. Only numbers, letters, -, and _ accepted. Max. length 64 characters.
     Default: osdu-legal-config
@@ -47,7 +47,7 @@ Resources:
     Type: 'AWS::S3::Bucket'
     DeletionPolicy: Delete
     Properties:
-      BucketName: !Sub ${Environment}-${LegalConfigBucketName}
+      BucketName: !Sub ${Environment}-${AWS::AccountId}-${LegalConfigBucketName}
 
   LegalConfigS3BucketPolicy:
     Type: AWS::S3::BucketPolicy
@@ -70,6 +70,6 @@ Resources:
 Outputs:
   LegalConfigS3BucketName:
     Description: The name of the OSDU legal config S3 bucket.
-    Value: !Ref LegalConfigBucketName
+    Value: !Ref S3BucketLegalConfig
     Export:
       Name: !Sub ${Environment}-S3BucketLegalConfig
\ No newline at end of file
diff --git a/provider/legal-aws/CloudFormation/Automated/ecs-cluster.yml b/provider/legal-aws/CloudFormation/Automated/ecs-cluster.yml
index 1c97b95cd..5cd3513ee 100644
--- a/provider/legal-aws/CloudFormation/Automated/ecs-cluster.yml
+++ b/provider/legal-aws/CloudFormation/Automated/ecs-cluster.yml
@@ -1,3 +1,17 @@
+# Copyright © Amazon Web Services
+#
+# 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
+#
+#      http://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.
+
 AWSTemplateFormatVersion: 2010-09-09
 Description: >-
   CloudFormation template for creating the resources used for the ECS cluster the application will
@@ -152,6 +166,32 @@ Parameters:
     MinValue: 256
     MaxValue: 131072
 
+  DomainName:
+    Description: >-
+      The optional custom DNS name for the ECS service's load balancer. If omitted, the site will only be accessible
+      via the ECS service's Application Load Balancer DNS name. This value is used in the creation and signing of
+      the service's SSL certificate. Leave blank is not using a custom domain for this deployment.
+    Type: String
+    Default: ''
+
+  HostedZoneName:
+    Description: >-
+      The name of the hosted zone (ex: for legal.osdu.slb.com, this would likely be osdu.slb.com).
+      Leave blank is not using a custom domain for this deployment.
+    Type: String
+    Default: ''
+
+  VersionNumber:
+    Description: The version number for the service jar being produced
+    Type: String
+    Default: '0.0.1'
+
+  ServiceName:
+    Description: >-
+      The service name associated with the jar package for the Dockerfile.
+    Type: String
+    Default: 'legal'
+
 Mappings:
   # This mapping is for the ECS-optimized edition of the November 13-14, 2019 release of the Amazon Linux 2 AMI
   # It will need to be periodically updated as new versions are released by Amazon.
@@ -191,7 +231,45 @@ Mappings:
     sa-east-1:
       AMIID: ami-0c947c117562538ee
 
+Conditions:
+  IncludeCustomDomain: !Not [!Equals [ !Ref DomainName, '' ]]
+  IsPortStandardSSL:
+    !Or [!Equals [ !Ref ECSPort, '443' ], !Equals [ !Ref ECSPort, '8443' ]]
+  IsLoadBalancerHTTPS: !And # HTTPS for ECS requires a custom domain, but CloudFront will still have HTTPS/SSL
+    - !Condition IncludeCustomDomain
+    - !Condition IsPortStandardSSL
+
 Resources:
+  # This sets up a Route 53 record for CloudFront if a custom domain is being used,
+  # otherwise a default cloudfront.net value will be used instead
+  CloudFrontDNSName:
+    Type: AWS::Route53::RecordSetGroup
+    Condition: IncludeCustomDomain
+    Properties:
+      HostedZoneName: !Join ['', [!Ref HostedZoneName, .]] # Route 53 requires a trailing period
+      RecordSets:
+        - Name: !Ref DomainName
+          Type: A
+          AliasTarget:
+            # This hosted zone ID is for ALL CloudFront distributions, always, and should be hard-coded
+            HostedZoneId: Z2FDTNDATAQYW2
+            DNSName: !GetAtt ECSCloudFrontDistribution.DomainName
+
+  # This sets up a Route 53 record for the ECS ALB origin if a custom domain is being used
+  ECSDNSName:
+    Type: AWS::Route53::RecordSetGroup
+    Condition: IncludeCustomDomain
+    Properties:
+      HostedZoneName: !Join ['', [!Ref HostedZoneName, .]] # Route 53 requires a trailing period
+      RecordSets:
+        - Name: !Join ['.', ['origin', !Ref DomainName]] # prefix the ECS origin record with 'origin.'
+          Type: A
+          AliasTarget:
+            HostedZoneId: !GetAtt ECSALB.CanonicalHostedZoneID # this value comes from the ALB attributes
+            DNSName: !GetAtt ECSALB.DNSName
+            EvaluateTargetHealth: true # Route 53 routes traffic to ECS targets based on their health checks
+    DependsOn: ECSALB
+
   CodeDeployApplication:
     Type: AWS::CodeDeploy::Application
     Properties:
@@ -211,11 +289,11 @@ Resources:
               AWS:
                 - !Sub arn:aws:iam::${AWS::AccountId}:root
                 - Fn::ImportValue:
-                    !Sub "${Environment}-${ApplicationName}-CodeBuildRoleArn"
+                    !Sub "${Environment}-CodeBuildRoleArn"
                 - Fn::ImportValue:
-                    !Sub "${Environment}-${ApplicationName}-CFNRoleArn"
+                    !Sub "${Environment}-CFNRoleArn"
                 - Fn::ImportValue:
-                    !Sub "${Environment}-${ApplicationName}-PipelineRoleArn"
+                    !Sub "${Environment}-PipelineRoleArn"
               Service:
                 - codebuild.amazonaws.com
             Action:
@@ -282,7 +360,9 @@ Resources:
             - Name: SNS_TOPIC_NAME
               Value: !Ref SNSTopicName
             - Name: S3_LEGAL_CONFIG_BUCKET
-              Value: !Ref LegalConfigS3BucketName
+              Value:
+                Fn::ImportValue:
+                  !Sub "${Environment}-S3BucketLegalConfig"
             - Name: JAVA_HEAP_MEMORY
               Value: !Ref ECSMemoryAllocation
       Volumes:
@@ -314,7 +394,16 @@ Resources:
           TargetGroupArn: !Ref 'ECSTargetGroup'
       LoadBalancerArn: !Ref 'ECSALB'
       Port: !Ref ECSPort
-      Protocol: HTTP
+      Protocol: !If [IsLoadBalancerHTTPS, HTTPS, HTTP]
+
+  LoadBalancerALBListenerCertificate:
+    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
+    Condition: IncludeCustomDomain
+    Properties:
+      Certificates:
+        - Fn::ImportValue:
+            !Sub "${Environment}-${ApplicationName}-LoadBalancerSSLCertificateArn"
+      ListenerArn: !Ref 'ALBListener'
 
   ECSALBPrimaryListenerRule:
     Type: AWS::ElasticLoadBalancingV2::ListenerRule
@@ -335,17 +424,92 @@ Resources:
     Properties:
       HealthCheckIntervalSeconds: 120
       HealthCheckPath: /api/legal/v1/_ah/liveness_check
-      HealthCheckProtocol: HTTP
+      HealthCheckProtocol: !If [IsLoadBalancerHTTPS, HTTPS, HTTP]
       HealthCheckTimeoutSeconds: 5
       HealthyThresholdCount: 2
-      Name: !Sub ECSTargetGroup-${ApplicationName}
+      Name: !Sub ECSTargetGroup-New-${ApplicationName}
       Port: !Ref ECSPort
-      Protocol: HTTP
+      Protocol: !If [IsLoadBalancerHTTPS, HTTPS, HTTP]
       UnhealthyThresholdCount: 2
       VpcId:
         Fn::ImportValue:
           !Sub "${Environment}-OSDU-VPC"
 
+  ECSCloudFrontDistribution:
+    Type: AWS::CloudFront::Distribution
+    DependsOn: ECSALB
+    Properties:
+      DistributionConfig:
+        Comment: 'Cloudfront Distribution pointing ALB Origin'
+        Origins:
+          - DomainName: !GetAtt 'ECSALB.DNSName'
+            Id: !Ref 'ECSALB'
+            CustomOriginConfig:
+              HTTPPort: !Ref ECSPort # The ports are the same because we'll only ever be accessing the ECS cluster over one protocol, as set in OriginProtocolPolicy below
+              HTTPSPort: !Ref ECSPort # The ports are the same because we'll only ever be accessing the ECS cluster over one protocol, as set in OriginProtocolPolicy below
+              OriginProtocolPolicy: !If [IsLoadBalancerHTTPS, https-only, http-only] # this only affects the origin, not CloudFront / the user's request
+              OriginKeepaliveTimeout: '60'
+              OriginReadTimeout: '60'
+              OriginSSLProtocols:
+                - TLSv1
+                - TLSv1.1
+                - TLSv1.2
+                - SSLv3
+        Enabled: true
+        HttpVersion: 'http2'
+        Aliases:
+          - Fn::If:
+              - IncludeCustomDomain
+              - !Ref DomainName
+              - !Ref AWS::NoValue
+        DefaultCacheBehavior:
+          AllowedMethods:
+            - GET
+            - HEAD
+            - OPTIONS
+            - PUT
+            - POST
+            - PATCH
+            - DELETE
+          Compress: true
+          TargetOriginId: !Ref 'ECSALB'
+          DefaultTTL: 5
+          MaxTTL: 30
+          ForwardedValues:
+            QueryString: true
+            Cookies:
+              Forward: all
+            Headers:
+              - Authorization
+              - Data-Partition-Id
+              - Content-Type
+              - Kind
+              - Limit
+              - Cursor
+          ViewerProtocolPolicy: redirect-to-https # CloudFront requests will always be HTTPS, regardless of the origin or the request
+        ViewerCertificate:
+          AcmCertificateArn:
+            Fn::If:
+              - IncludeCustomDomain
+              - Fn::ImportValue:
+                  !Sub "${Environment}-${ApplicationName}-LoadBalancerSSLCertificateArn"
+              - Ref: AWS::NoValue
+          CloudFrontDefaultCertificate:
+            Fn::If:
+              - IncludeCustomDomain
+              - Ref: AWS::NoValue
+              - true
+          SslSupportMethod:
+            Fn::If:
+              - IncludeCustomDomain
+              - sni-only # sni-only is free; 'vip' is the only other option, which allows viewers without Server Name Indication (SNI) support by using dedicated IP addresses, but it costs $600/mo per SSL certificate
+              - Ref: AWS::NoValue
+          MinimumProtocolVersion:
+            Fn::If:
+              - IncludeCustomDomain
+              - TLSv1
+              - Ref: AWS::NoValue # this is not used when using the default CloudFront certificate (which is always TLSv1)
+
   ECSAutoScalingGroup:
     Type: AWS::AutoScaling::AutoScalingGroup
     Properties:
@@ -534,8 +698,40 @@ Outputs:
     Export:
       Name: !Sub ${Environment}-${ApplicationName}-EcsAlbUrl
 
+  ECSALBCustomDNSName:
+    Description: The custom DNS name of the ECS service's ALB origin.
+    Condition: IncludeCustomDomain
+    Value: !Join ['.', ['origin', !Ref DomainName]]
+    Export:
+      Name: !Sub ${Environment}-${ApplicationName}-EcsAlbCustomDnsName
+
+  ECSCloudFrontCustomDNSName:
+    Description: The custom DNS name of the ECS service's CloudFront Distribution.
+    Condition: IncludeCustomDomain
+    Value: !Ref DomainName
+    Export:
+      Name: !Sub ${Environment}-${ApplicationName}-EcsCloudFrontCustomDnsName
+
+  ECSCloudFrontDomainName:
+    Description: The custom DNS name of the ECS service's CloudFront Distribution.
+    Value: !GetAtt ECSCloudFrontDistribution.DomainName
+    Export:
+      Name: !Sub ${Environment}-${ApplicationName}-EcsCloudFrontDomainName
+
   TaskDefinitionArn:
     Description: The ARN of the Search Service ECS task definition.
     Value: !Ref 'TaskDefinition'
     Export:
       Name: !Sub ${Environment}-${ApplicationName}-EcsTaskDefinitionArn
+
+  JarVersionNumber:
+    Description: The service name associated with the JAR package for the Dockerfile.
+    Value: !Ref 'VersionNumber'
+    Export:
+      Name: !Sub ${Environment}-${ApplicationName}-JarVersionNumber
+
+  JarServiceName:
+    Description: The service name associated with the JAR package for the Dockerfile.
+    Value: !Ref 'ServiceName'
+    Export:
+      Name: !Sub ${Environment}-${ApplicationName}-JarServiceName
\ No newline at end of file
diff --git a/provider/legal-aws/CloudFormation/Automated/ecs-network.yml b/provider/legal-aws/CloudFormation/Automated/ecs-network.yml
index 1b7037389..1d8c2fa23 100644
--- a/provider/legal-aws/CloudFormation/Automated/ecs-network.yml
+++ b/provider/legal-aws/CloudFormation/Automated/ecs-network.yml
@@ -1,3 +1,17 @@
+# Copyright © Amazon Web Services
+#
+# 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
+#
+#      http://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.
+
 AWSTemplateFormatVersion: 2010-09-09
 Description: >-
   CloudFormation template for creating the network resources used for the ECS cluster the application will
@@ -38,11 +52,49 @@ Parameters:
   ECSPort:
     Description: The port that the ECS Service will listen on.
     Type: Number
-    Default: 80
+    Default: 443
     MinValue: 1
     MaxValue: 65535
 
+  DomainName:
+    Description: >-
+      The optional custom DNS name for the service's load balancer. If omitted, the site will only be accessible
+      via the ECS service's Application Load Balancer DNS name. This value is used in the creation and signing of
+      the service's SSL certificate. Leave blank for none.
+    Type: String
+    Default: ''
+
+  AcmCertificateArn:
+    Description: >-
+      The Amazon Resource Name (ARN) of an existing AWS Certificate Manager (ACM) certificate.
+      If omitted, a new SSL certified will be requested/generated (only if the custom domain name
+      parameter is provided, otherwise the ECS service's ALB will not use SSL/HTTPS).
+    Type: String
+    AllowedPattern: "^(|arn:aws:acm:.*)$"
+    Default: ''
+
+Conditions:
+  IncludeCustomDomain: !Not [!Equals [ !Ref DomainName, '' ]]
+  UseExistingACMSSLCertificate: !And
+    - !Not [!Equals [ !Ref AcmCertificateArn, '' ]]
+    - !Condition IncludeCustomDomain
+  ShouldRequestNewSSLCertificate: !And
+    - !Not [!Condition UseExistingACMSSLCertificate]
+    - !Condition IncludeCustomDomain
+  ShouldExportSSLCertificate: !Or
+    - !Condition IncludeCustomDomain
+    - !Condition UseExistingACMSSLCertificate
+
 Resources:
+  # If an existing SSL certificate is not provided, but a custom domain is, request one
+  LoadBalancerSSLCertificate:
+    Type: 'AWS::CertificateManager::Certificate'
+    Condition: ShouldRequestNewSSLCertificate
+    Properties:
+      DomainName: !Ref DomainName
+      SubjectAlternativeNames:
+        - !Join ['.', ['origin', !Ref DomainName]] #
+
   ECSSecurityGroup:
     Type: AWS::EC2::SecurityGroup
     Properties:
@@ -52,7 +104,7 @@ Resources:
         Fn::ImportValue:
           !Sub "${Environment}-OSDU-VPC"
 
-  # Public access to ECS Listening Port
+  # Public access to the specified ECS Listening Port
   ECSSecurityGroupECSListenerInbound:
     Type: AWS::EC2::SecurityGroupIngress
     Properties:
@@ -62,37 +114,8 @@ Resources:
       ToPort: !Ref ECSPort
       CidrIp: 0.0.0.0/0
 
-  # Public access to port 443
-  ECSSecurityGroupHTTPSInbound:
-    Type: AWS::EC2::SecurityGroupIngress
-    Properties:
-      GroupId: !Ref 'ECSSecurityGroup'
-      IpProtocol: tcp
-      FromPort: '443'
-      ToPort: '443'
-      CidrIp: 0.0.0.0/0
-
-  # Public access to port 8080
-  ECSSecurityGroupHTTPAltInbound:
-    Type: AWS::EC2::SecurityGroupIngress
-    Properties:
-      GroupId: !Ref 'ECSSecurityGroup'
-      IpProtocol: tcp
-      FromPort: '8080'
-      ToPort: '8080'
-      CidrIp: 0.0.0.0/0
-
-  # Public access to port 8443
-  ECSSecurityGroupHTTPSAltInbound:
-    Type: AWS::EC2::SecurityGroupIngress
-    Properties:
-      GroupId: !Ref 'ECSSecurityGroup'
-      IpProtocol: tcp
-      FromPort: '8443'
-      ToPort: '8443'
-      CidrIp: 0.0.0.0/0
-
-  # SSH access for instances in our VPC's jump box subnet group (coming soon – will be part of the Util CFN)
+  # SSH access for instances in our VPC's jump box subnet group
+  # TODO: Update when the jump box is created as a part of the Util CFN, for now it is public
   ECSSecurityGroupSSHInbound:
     Type: AWS::EC2::SecurityGroupIngress
     Properties:
@@ -102,7 +125,7 @@ Resources:
       ToPort: '22'
       CidrIp: 0.0.0.0/0
 
-  # Open Application Load Balancer port range to itself
+  # Open Application Load Balancer port range to self-access
   ECSSecurityGroupALBports:
     Type: AWS::EC2::SecurityGroupIngress
     Properties:
@@ -118,3 +141,10 @@ Outputs:
     Value: !Ref 'ECSSecurityGroup'
     Export:
       Name: !Sub ${Environment}-${ApplicationName}-EcsNetworkSecurityGroupId
+
+  LoadBalancerSSLCertificateArn:
+    Condition: ShouldExportSSLCertificate
+    Description: The ARN of the SSL certificate to be used for both ECS and CloudFront (includes both DNS names).
+    Value: !If [UseExistingACMSSLCertificate, !Ref AcmCertificateArn, !Ref 'LoadBalancerSSLCertificate']
+    Export:
+      Name: !Sub ${Environment}-${ApplicationName}-LoadBalancerSSLCertificateArn
diff --git a/provider/legal-aws/CloudFormation/Automated/sns-topic.yml b/provider/legal-aws/CloudFormation/Automated/sns-topic.yml
index d2bcc0042..20893e56f 100644
--- a/provider/legal-aws/CloudFormation/Automated/sns-topic.yml
+++ b/provider/legal-aws/CloudFormation/Automated/sns-topic.yml
@@ -78,11 +78,11 @@ Resources:
               - OSDULegalSQSQueue
               - Arn
           Protocol: sqs
-        - Endpoint:
-            Fn::GetAtt:
-              - OSDUComplianceTrigger
-              - Arn
-          Protocol: lambda
+#        - Endpoint:
+#            Fn::GetAtt:
+#              - OSDUComplianceTrigger
+#              - Arn
+#          Protocol: lambda
 
 
   OSDULegalSQSQueue:
@@ -90,18 +90,6 @@ Resources:
     Properties:
       QueueName: !Sub ${Environment}-${SQSQueueName}
 
-  OSDUComplianceTrigger:
-    Type: AWS::Lambda::Function
-    Properties:
-      FunctionName: !Sub ${Environment}-${ComplianceTriggerName}
-      Handler: compliance.ComplianceTriggerFunction::handleRequest
-      Runtime: java8
-      MemorySize: 512
-      Role: arn:aws:iam::888733619319:role/dev-os-legal-lambda-ComplianceTriggerFunctionRole-1RGCZVRE1V91S
-      Code:
-        S3Bucket: dev-os-legal-lambda-cloudformation
-        S3Key: 89ed4d883fb1fb7f6da4a507e607390d
-
   OSDUQueuePolicy:
     Type: AWS::SQS::QueuePolicy
     Properties:
diff --git a/provider/legal-aws/CloudFormation/JarDeploy/CodePipeline-JarDeploy.yml b/provider/legal-aws/CloudFormation/JarDeploy/CodePipeline-JarDeploy.yml
new file mode 100644
index 000000000..a09e0b76e
--- /dev/null
+++ b/provider/legal-aws/CloudFormation/JarDeploy/CodePipeline-JarDeploy.yml
@@ -0,0 +1,252 @@
+# Copyright © Amazon Web Services
+#
+# 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
+#
+#      http://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.
+
+AWSTemplateFormatVersion: 2010-09-09
+
+Description: >
+  This CloudFormation script creates the deployment pipeline for OSDU's legal service. The CodePipeline
+  should automatically trigger whenever commits are made on the tracked branch. The start and end
+  of the CodePipeline should trigger a SNS alert to keep track of when the deployment has started
+  and when it finishes.
+
+Parameters:
+  Environment:
+    Description: Environment Name. Defaults to 'dev'. Can only be dev/uat/prod.
+    Type: String
+    AllowedValues:
+      - dev
+      - uat
+      - prod
+    Default: dev
+
+  DeploymentRegion:
+    Description: The AWS region to deploy the application to. The default is us-east-1.
+    Type: String
+    Default: us-east-1
+
+  SNSNotificationEmail:
+    Description: The email address to send SNS notifications about the build to.
+    Type: String
+    Default: barclay.walsh@parivedasolutions.com
+
+  CodeCommitRepositoryName:
+    Description: The name of the Code Commit Repository that the CodePipeline source is connected to.
+    Type: String
+    Default: os-legal
+
+  JarServiceBase:
+    Description: The name of the service base path for the JAR files (e.g. 'legal').
+    Type: String
+    Default: legal
+
+  CodeCommitBranchName:
+    Description: The name of the Code Commit branch that the CodePipeline source is connected to.
+    Type: String
+    Default: dev
+
+Resources:
+  ArtifactStoreBucket:
+    Type: AWS::S3::Bucket
+    DeletionPolicy: Delete
+    Properties:
+      VersioningConfiguration:
+        Status: Enabled
+
+  ArtifactStoreBucketPolicy:
+    Type: AWS::S3::BucketPolicy
+    Properties:
+      Bucket: !Ref ArtifactStoreBucket
+      PolicyDocument:
+        Statement:
+          - Action:
+              - s3:*
+            Effect: Allow
+            Resource:
+              - !Sub arn:aws:s3:::${ArtifactStoreBucket}
+              - !Sub arn:aws:s3:::${ArtifactStoreBucket}/*
+            Principal:
+              AWS:
+                - !Sub arn:aws:iam::${AWS::AccountId}:root
+                - !ImportValue
+                  'Fn::Sub': '${Environment}-CodeBuildRoleArn'
+                - !ImportValue
+                  'Fn::Sub': '${Environment}-PipelineRoleArn'
+                - !ImportValue
+                  'Fn::Sub': '${Environment}-CFNRoleArn'
+
+  CachingBucket:
+    Type: AWS::S3::Bucket
+    DeletionPolicy: Delete
+    Properties:
+      VersioningConfiguration:
+        Status: Enabled
+
+  CachingBucketPolicy:
+    Type: AWS::S3::BucketPolicy
+    Properties:
+      Bucket: !Ref CachingBucket
+      PolicyDocument:
+        Statement:
+          - Action:
+              - s3:*
+            Effect: Allow
+            Resource:
+              - !Sub arn:aws:s3:::${CachingBucket}
+              - !Sub arn:aws:s3:::${CachingBucket}/*
+            Principal:
+              AWS:
+                - !Sub arn:aws:iam::${AWS::AccountId}:root
+                - !ImportValue
+                  'Fn::Sub': '${Environment}-CodeBuildRoleArn'
+                - !ImportValue
+                  'Fn::Sub': '${Environment}-PipelineRoleArn'
+                - !ImportValue
+                  'Fn::Sub': '${Environment}-CFNRoleArn'
+
+  SNSCodePipelineDeploymentFailed:
+    Type: AWS::SNS::Topic
+    Properties:
+      Subscription:
+        - Endpoint: !Ref SNSNotificationEmail
+          Protocol: email
+      TopicName: !Sub '${Environment}-OS-Legal-Deployment-CodePipeline-JarDeploy-Failed'
+
+  EventRuleCodePipelineFailed:
+    Type: AWS::Events::Rule
+    Properties:
+      Description: Triggered whenever the CodePipeline deployment stage has failed.
+      EventPattern:
+        source:
+          - "aws.codepipeline"
+        detail-type:
+          - "CodePipeline Stage Execution State Change"
+        detail:
+          state:
+            - "FAILED"
+          pipeline:
+            - !Sub '${Environment}-OSDU-OS-Legal-CodePipeline-JarDeploy'
+
+      Name: !Sub ${Environment}-CodePipelineEventRule-${CodeCommitRepositoryName}-JarDeploy
+      Targets:
+      -
+        Arn:
+          !Ref SNSCodePipelineDeploymentFailed
+        Id: "Deployment-CodePipeline-JarDeploy-Failed"
+        InputTransformer:
+          InputPathsMap:
+            pipeline : "$.detail.pipeline"
+          InputTemplate: '"The Pipeline <pipeline> has failed."'
+
+  Pipeline:
+    Type: AWS::CodePipeline::Pipeline
+    Properties:
+      ArtifactStore:
+        Location: !Ref ArtifactStoreBucket
+        Type: S3
+      Name: !Sub '${Environment}-OSDU-OS-Legal-CodePipeline-JarDeploy'
+      RoleArn: !ImportValue
+        'Fn::Sub': '${Environment}-PipelineRoleArn'
+      Stages:
+        - Name: Source
+          Actions:
+            - Name: Source
+              ActionTypeId:
+                Category: Source
+                Owner: AWS
+                Provider: CodeCommit
+                Version: '1'
+              Configuration:
+                BranchName: !Ref CodeCommitBranchName
+                RepositoryName: !Ref CodeCommitRepositoryName
+              OutputArtifacts:
+                - Name: Source
+              RunOrder: '1'
+
+        - Name: CodeBuild
+          Actions:
+            - Name: Jar-CodeBuild
+              ActionTypeId:
+                Category: Build
+                Owner: AWS
+                Provider: CodeBuild
+                Version: '1'
+              InputArtifacts:
+                - Name: Source
+              OutputArtifacts:
+                - Name: Jar-CodeBuild
+              Configuration:
+                ProjectName: !Ref JarCodeBuild
+              RunOrder: '2'
+
+  JarCodeBuild:
+    Type: AWS::CodeBuild::Project
+    Properties:
+      Name: !Sub ${Environment}-jar-codebuild-${CodeCommitRepositoryName}
+      Description: >
+        CodeBuild commands which handle setting environment variables, along with the
+        build, test, and packaging of the .jar file. It then uses the AWS CLI to copy
+        the versioned JAR and associated build files to the shared jar-deploy S3 bucket.
+      ServiceRole: !ImportValue
+        'Fn::Sub': '${Environment}-CodeBuildRoleArn'
+      Artifacts:
+        Type: S3
+        Location: !Ref ArtifactStoreBucket
+        Name: !Sub ${Environment}-jar-codebuild
+      Environment:
+        Type: LINUX_CONTAINER
+        ComputeType: BUILD_GENERAL1_SMALL
+        Image: aws/codebuild/standard:2.0
+        EnvironmentVariables:
+          - Name: ENVIRONMENT
+            Type: PLAINTEXT
+            Value: !Ref Environment
+          - Name: AWS_ACCOUNT_ID
+            Type: PLAINTEXT
+            Value: !Ref AWS::AccountId
+          - Name: AWS_REGION
+            Type: PLAINTEXT
+            Value: !Ref DeploymentRegion
+          - Name: APPLICATION_NAME
+            Type: PLAINTEXT
+            Value: !Ref CodeCommitRepositoryName
+          - Name: JAR_SERVICE_BASE
+            Type: PLAINTEXT
+            Value: !Ref JarServiceBase
+          - Name: M2_REPO_S3_BUCKET
+            Type: PLAINTEXT
+            Value: !Sub "${Environment}-${AWS::AccountId}-persistent-maven-m2-bucket"
+          - Name: JAR_DEPLOY_S3_BUCKET
+            Type: PLAINTEXT
+            Value: !Sub ${Environment}-${AWS::AccountId}-osdu-jar-deploy
+        PrivilegedMode: true
+      Source:
+        BuildSpec: ./provider/legal-aws/buildspec-jar-deploy.yml
+        Location: !Sub https://git-codecommit.${AWS::Region}.amazonaws.com/v1/repos/${CodeCommitRepositoryName}
+        Type: CODECOMMIT
+      Cache:
+        Type: S3
+        Location: !Sub ${CachingBucket}/${Environment}
+      TimeoutInMinutes: 15
+      VpcConfig:
+        SecurityGroupIds:
+          - Fn::ImportValue:
+              !Sub "${Environment}-OSDU-CodeBuildSecurityGroup"
+        Subnets:
+          - Fn::ImportValue:
+              !Sub "${Environment}-OSDU-PrivateSubnet-AZ1"
+          - Fn::ImportValue:
+              !Sub "${Environment}-OSDU-PrivateSubnet-AZ2"
+        VpcId:
+          Fn::ImportValue:
+            !Sub "${Environment}-OSDU-VPC"
diff --git a/provider/legal-aws/CloudFormation/Manual/01-CreateCodePipeline.yml b/provider/legal-aws/CloudFormation/Manual/01-CreateCodePipeline.yml
index c4ff88bd7..b22b0d04a 100644
--- a/provider/legal-aws/CloudFormation/Manual/01-CreateCodePipeline.yml
+++ b/provider/legal-aws/CloudFormation/Manual/01-CreateCodePipeline.yml
@@ -61,31 +61,6 @@ Parameters:
     Default: provider/legal-aws/CloudFormation/Master/os-legal-master.yml
 
 Resources:
-  S3BucketCloudFormation:
-    Type: 'AWS::S3::Bucket'
-    DeletionPolicy: Delete
-    Properties:
-      BucketName: !Sub ${Environment}-os-legal-cloudformation-scripts
-
-  CloudFormationS3BucketPolicy:
-    Type: AWS::S3::BucketPolicy
-    Properties:
-      Bucket: !Ref S3BucketCloudFormation
-      PolicyDocument:
-        Statement:
-          - Action:
-              - s3:*
-            Effect: Allow
-            Resource:
-              - !Sub arn:aws:s3:::${S3BucketCloudFormation}
-              - !Sub arn:aws:s3:::${S3BucketCloudFormation}/*
-            Principal:
-              AWS:
-                - !Sub arn:aws:iam::${AWS::AccountId}:root
-                - !GetAtt [CodeBuildRole,Arn]
-                - !GetAtt [PipelineRole,Arn]
-                - !GetAtt [CFNRole,Arn]
-
   ArtifactStoreBucket:
     Type: AWS::S3::Bucket
     DeletionPolicy: Delete
@@ -108,9 +83,12 @@ Resources:
             Principal:
               AWS:
                 - !Sub arn:aws:iam::${AWS::AccountId}:root
-                - !GetAtt [CodeBuildRole,Arn]
-                - !GetAtt [PipelineRole,Arn]
-                - !GetAtt [CFNRole,Arn]
+                - !ImportValue
+                  'Fn::Sub': '${Environment}-CodeBuildRoleArn'
+                - !ImportValue
+                  'Fn::Sub': '${Environment}-PipelineRoleArn'
+                - !ImportValue
+                  'Fn::Sub': '${Environment}-CFNRoleArn'
 
   CachingBucket:
     Type: AWS::S3::Bucket
@@ -134,9 +112,12 @@ Resources:
             Principal:
               AWS:
                 - !Sub arn:aws:iam::${AWS::AccountId}:root
-                - !GetAtt [CodeBuildRole,Arn]
-                - !GetAtt [PipelineRole,Arn]
-                - !GetAtt [CFNRole,Arn]
+                - !ImportValue
+                    'Fn::Sub': '${Environment}-CodeBuildRoleArn'
+                - !ImportValue
+                    'Fn::Sub': '${Environment}-PipelineRoleArn'
+                - !ImportValue
+                    'Fn::Sub': '${Environment}-CFNRoleArn'
 
   SNSCodePipelineDeploymentFailed:
     Type: AWS::SNS::Topic
@@ -179,7 +160,8 @@ Resources:
         Location: !Ref ArtifactStoreBucket
         Type: S3
       Name: !Sub '${Environment}-OSDU-OS-Legal-CodePipeline'
-      RoleArn: !GetAtt [PipelineRole, Arn]
+      RoleArn: !ImportValue
+        'Fn::Sub': '${Environment}-PipelineRoleArn'
       Stages:
         - Name: Source
           Actions:
@@ -225,7 +207,8 @@ Resources:
               Configuration:
                 ActionMode: CREATE_UPDATE
                 Capabilities: CAPABILITY_NAMED_IAM
-                RoleArn: !GetAtt [ CFNRole, Arn ]
+                RoleArn: !ImportValue
+                  'Fn::Sub': '${Environment}-CFNRoleArn'
                 StackName: !Sub ${Environment}-${MasterStackName}
                 TemplatePath: !Sub "Source::${MasterTemplateName}"
                 TemplateConfiguration: !Sub "Source::provider/legal-aws/CloudFormation/Params/${Environment}.template_configuration.json"
@@ -252,8 +235,8 @@ Resources:
     Properties:
       Name: !Sub ${Environment}-pre-deployment-codebuild-${CodeCommitRepositoryName}
       Description: CodeBuild commands which run prior to the CloudFormation deployment.
-      ServiceRole:
-        Fn::GetAtt: [ CodeBuildRole, Arn ]
+      ServiceRole: !ImportValue
+        'Fn::Sub': '${Environment}-CodeBuildRoleArn'
       Artifacts:
         Type: S3
         Location: !Ref ArtifactStoreBucket
@@ -274,7 +257,11 @@ Resources:
             Value: !Ref DeploymentRegion
           - Name: CFN_S3_BUCKET
             Type: PLAINTEXT
-            Value: !Sub ${Environment}-os-legal-cloudformation-scripts
+            Value: !ImportValue
+              'Fn::Sub': '${Environment}-S3BucketCloudFormation'
+          - Name: APPLICATION_NAME
+            Type: PLAINTEXT
+            Value: !Ref CodeCommitRepositoryName
         PrivilegedMode: false
       Source:
         BuildSpec: ./provider/legal-aws/buildspec-pre-deploy.yml
@@ -287,8 +274,8 @@ Resources:
     Properties:
       Name: !Sub ${Environment}-post-deployment-codebuild-${CodeCommitRepositoryName}
       Description: CodeBuild commands which run after the CloudFormation deployment.
-      ServiceRole:
-        Fn::GetAtt: [ CodeBuildRole, Arn ]
+      ServiceRole: !ImportValue
+        'Fn::Sub': '${Environment}-CodeBuildRoleArn'
       Artifacts:
         Type: S3
         Location: !Ref ArtifactStoreBucket
@@ -307,9 +294,6 @@ Resources:
           - Name: AWS_REGION
             Type: PLAINTEXT
             Value: !Ref DeploymentRegion
-          - Name: VSTS_FEED_USER
-            Type: PLAINTEXT
-            Value: '{{resolve:secretsmanager:dev-VSTSFeedToken:SecretString:vsts_feed_user}}'
           - Name: VSTS_FEED_TOKEN
             Type: PLAINTEXT
             Value: '{{resolve:secretsmanager:dev-VSTSFeedToken:SecretString:vsts_feed_token}}'
@@ -322,6 +306,9 @@ Resources:
           - Name: APPLICATION_NAME
             Type: PLAINTEXT
             Value: !Ref CodeCommitRepositoryName
+          - Name: M2_REPO_S3_BUCKET
+            Type: PLAINTEXT
+            Value: !Sub "${Environment}-${AWS::AccountId}-persistent-maven-m2-bucket"
         PrivilegedMode: true
       Source:
         BuildSpec: ./provider/legal-aws/buildspec-post-deploy.yml
@@ -343,297 +330,3 @@ Resources:
         VpcId:
           Fn::ImportValue:
             !Sub "${Environment}-OSDU-VPC"
-
-  CFNRole:
-    Type: AWS::IAM::Role
-    Properties:
-      AssumeRolePolicyDocument:
-        Statement:
-        - Action: ['sts:AssumeRole']
-          Effect: Allow
-          Principal:
-            Service: [cloudformation.amazonaws.com]
-        Version: '2012-10-17'
-      Path: /
-      Policies:
-        - PolicyName: !Sub CloudFormationRole-${CodeCommitRepositoryName}
-          PolicyDocument:
-            Version: '2012-10-17'
-            Statement:
-              -
-                Action:
-                  - 's3:*'
-                  - 'ec2:*'
-                  - 'apigateway:*'
-                  - 'cloudwatch:*'
-                  - 'events:*'
-                  - 'logs:*'
-                  - 'xray:*'
-                  - 'lambda:*'
-                  - 'rds:*'
-                  - 'codepipeline:*'
-                  - 'codecommit:*'
-                  - 'cloudformation:*'
-                  - 'dynamodb:*'
-                  - 'application-autoscaling:*'
-                  - 'autoscaling:*'
-                  - 'states:*'
-                  - 'iam:CreateUser'
-                  - 'iam:UpdateUser'
-                  - 'iam:DeleteUser'
-                  - 'iam:CreateAccessKey'
-                  - 'iam:UpdateAccessKey'
-                  - 'iam:DeleteAccessKey'
-                  - 'iam:Delete*'
-                  - "iam:List*"
-                  - "iam:Get*"
-                  - "iam:CreateServiceSpecificCredential"
-                  - "iam:DeactivateMFADevice"
-                  - "iam:GenerateServiceLastAccessedDetails"
-                  - "iam:UpdateOpenIDConnectProviderThumbprint"
-                  - "iam:PutRolePolicy"
-                  - "iam:AddRoleToInstanceProfile"
-                  - "iam:SimulateCustomPolicy"
-                  - "iam:UploadSSHPublicKey"
-                  - "iam:UpdateServiceSpecificCredential"
-                  - "iam:RemoveClientIDFromOpenIDConnectProvider"
-                  - "iam:UpdateRoleDescription"
-                  - "iam:UpdateServerCertificate"
-                  - "iam:CreateInstanceProfile"
-                  - "iam:GenerateCredentialReport"
-                  - "iam:UntagRole"
-                  - "iam:PutRolePermissionsBoundary"
-                  - "iam:TagRole"
-                  - "iam:ResetServiceSpecificCredential"
-                  - "iam:PassRole"
-                  - "iam:EnableMFADevice"
-                  - "iam:ResyncMFADevice"
-                  - "iam:UpdateSAMLProvider"
-                  - "iam:CreatePolicy"
-                  - "iam:CreateServiceLinkedRole"
-                  - "iam:UpdateRole"
-                  - "iam:AddClientIDToOpenIDConnectProvider"
-                  - "iam:SetDefaultPolicyVersion"
-                  - "iam:UpdateAssumeRolePolicy"
-                  - "iam:RemoveRoleFromInstanceProfile"
-                  - "iam:CreateRole"
-                  - "iam:AttachRolePolicy"
-                  - "iam:CreateLoginProfile"
-                  - "iam:DetachRolePolicy"
-                  - "iam:AttachUserPolicy"
-                  - "iam:DetachUserPolicy"
-                  - "iam:PutUserPolicy"
-                  - "iam:*UserPolicy"
-                  - "iam:SimulatePrincipalPolicy"
-                  - "iam:CreateAccountAlias"
-                  - "iam:ChangePassword"
-                  - "iam:UpdateLoginProfile"
-                  - "iam:UpdateAccessKey"
-                  - "iam:UpdateSSHPublicKey"
-                  - "iam:UpdateAccountPasswordPolicy"
-                  - "iam:CreateSAMLProvider"
-                  - "iam:CreateVirtualMFADevice"
-                  - "iam:CreateAccessKey"
-                  - "iam:AddUserToGroup"
-                  - "iam:RemoveUserFromGroup"
-                  - "iam:CreatePolicyVersion"
-                  - "iam:UploadSigningCertificate"
-                  - "iam:TagUser"
-                  - "iam:CreateOpenIDConnectProvider"
-                  - "iam:UploadServerCertificate"
-                  - "iam:UntagUser"
-                  - "iam:UpdateSigningCertificate"
-                  - 'sns:*'
-                  - 'sqs:*'
-                  - 'secretsmanager:*'
-                  - 'acm:*'
-                  - 'kms:*'
-                  - 'cloudfront:*'
-                  - 'route53:*'
-                  - 'route53domains:*'
-                  - 'elasticache:*'
-                  - 'ecr:*'
-                  - 'codedeploy:*'
-                  - 'elasticloadbalancing:*'
-                  - 'ecs:*'
-                  - 'servicediscovery:CreatePrivateDnsNamespace'
-                  - 'servicediscovery:CreateService'
-                  - 'servicediscovery:GetNamespace'
-                  - 'servicediscovery:GetOperation'
-                  - 'servicediscovery:GetService'
-                  - 'servicediscovery:ListNamespaces'
-                  - 'servicediscovery:ListServices'
-                  - 'servicediscovery:UpdateService'
-                  - 'servicediscovery:DeleteService'
-                Effect: Allow
-                Resource: '*'
-
-  CodeBuildRole:
-    Type: "AWS::IAM::Role"
-    Properties:
-      RoleName: !Sub CodeBuildRole-${CodeCommitRepositoryName}
-      AssumeRolePolicyDocument:
-        Version: "2012-10-17"
-        Statement:
-          -
-            Effect: "Allow"
-            Principal:
-              Service:
-                - "codebuild.amazonaws.com"
-            Action:
-              - "sts:AssumeRole"
-      Path: /service-role/
-      Policies:
-        -
-          PolicyName: !Sub CodeBuildNestedCFNAccessPolicy-${CodeCommitRepositoryName}
-          PolicyDocument:
-            Version: "2012-10-17"
-            Statement:
-              -
-                Effect: "Allow"
-                Action:
-                  - "cloudformation:Get*"
-                  - "cloudformation:Describe*"
-                  - "cloudformation:List*"
-                Resource:
-                  - '*'
-              -
-                Effect: "Allow"
-                Action:
-                  - "codebuild:StartBuild"
-                Resource:
-                  - Fn::Sub: arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/*
-              -
-                Effect: "Allow"
-                Action:
-                  - "codecommit:ListBranches"
-                  - "codecommit:ListRepositories"
-                  - "codecommit:BatchGetRepositories"
-                  - "codecommit:Get*"
-                  - "codecommit:GitPull"
-                Resource:
-                  - !Sub arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${CodeCommitRepositoryName}
-              -
-                Effect: "Allow"
-                Action:
-                  - "ec2:*"
-                  - "cloudformation:ValidateTemplate"
-                  - "elasticloadbalancing:Describe*"
-                  - "autoscaling:Describe*"
-                  - "iam:Get*"
-                  - "iam:List*"
-                  - "logs:Describe*"
-                  - "logs:Get*"
-                  - "tag:Get*"
-                  - "ecr:*"
-                  - "codedeploy:*"
-                  - "ecs:*"
-                Resource:
-                  - "*"
-              -
-                Effect: "Allow"
-                Action:
-                  - "logs:CreateLogGroup"
-                  - "logs:CreateLogStream"
-                  - "logs:PutLogEvents"
-                Resource:
-                  - Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*
-              -
-                Effect: "Allow"
-                Action:
-                  - "s3:*"
-                Resource: '*'
-              -
-                Effect: "Allow"
-                Action:
-                  - "lambda:UpdateFunctionCode"
-                  - "lambda:UpdateFunctionConfiguration"
-                  - "lambda:PublishLayerVersion"
-                  - "lambda:GetLayerVersion"
-                Resource: '*'
-              -
-                Effect: "Allow"
-                Action:
-                  - "apigateway:GET"
-                  - "apigateway:POST"
-                Resource: '*'
-
-  PipelineRole:
-    Type: AWS::IAM::Role
-    Properties:
-      AssumeRolePolicyDocument:
-        Statement:
-        - Action: ['sts:AssumeRole']
-          Effect: Allow
-          Principal:
-            Service: [codepipeline.amazonaws.com]
-        Version: '2012-10-17'
-      Path: /
-      Policies:
-        - PolicyName: !Sub CodePipelineAccess-${CodeCommitRepositoryName}
-          PolicyDocument:
-            Version: '2012-10-17'
-            Statement:
-              - Action:
-                - 's3:*'
-                - 'cloudformation:CreateStack'
-                - 'cloudformation:DescribeStacks'
-                - 'cloudformation:DeleteStack'
-                - 'cloudformation:UpdateStack'
-                - 'cloudformation:CreateChangeSet'
-                - 'cloudformation:ExecuteChangeSet'
-                - 'cloudformation:DeleteChangeSet'
-                - 'cloudformation:DescribeChangeSet'
-                - 'cloudformation:SetStackPolicy'
-                - 'cloudformation:ValidateTemplate'
-                - 'iam:PassRole'
-                - 'sns:Publish'
-                - 'lambda:ListFunctions'
-                - 'lambda:InvokeFunction'
-                - 'ec2:Describe*'
-                - 'ec2:Get*'
-                - 'ec2:Search*'
-                - 'ec2:*Vpc*'
-                - 'ec2:*Gateway'
-                - 'ec2:*Tags'
-                - 'ec2:*Subnet*'
-                - 'ec2:*Route*'
-                - 'ec2:*SecurityGroup'
-                - 'ec2:allocate*'
-                - 'ec2:release*'
-                Effect: Allow
-                Resource: '*'
-              - Action:
-                - 'codecommit:GetUploadArchiveStatus'
-                - 'codecommit:CancelUploadArchive'
-                - 'codecommit:GetBranch'
-                - 'codecommit:GetCommit'
-                - 'codecommit:GetUploadStatus'
-                - 'codecommit:UploadArchive'
-                Effect: Allow
-                Resource: '*'
-              -
-                Effect: "Allow"
-                Action:
-                  - "codebuild:*"
-                Resource:
-                  - Fn::Sub: arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/*
-
-Outputs:
-  CodeBuildRoleArn:
-    Description: The ARN of the role used by the CodeBuild projects.
-    Value: !GetAtt CodeBuildRole.Arn
-    Export:
-      Name: !Sub ${Environment}-${CodeCommitRepositoryName}-CodeBuildRoleArn
-
-  CFNRoleArn:
-    Description: The ARN of the role used by CloudFormation templates run from the automated pipeline.
-    Value: !GetAtt CFNRole.Arn
-    Export:
-      Name: !Sub ${Environment}-${CodeCommitRepositoryName}-CFNRoleArn
-
-  PipelineRoleArn:
-    Description: The ARN of the role used by the application's CodePipeline.
-    Value: !GetAtt PipelineRole.Arn
-    Export:
-      Name: !Sub ${Environment}-${CodeCommitRepositoryName}-PipelineRoleArn
diff --git a/provider/legal-aws/CloudFormation/Master/os-legal-master.yml b/provider/legal-aws/CloudFormation/Master/os-legal-master.yml
index 6ac45aa6a..e335f660c 100644
--- a/provider/legal-aws/CloudFormation/Master/os-legal-master.yml
+++ b/provider/legal-aws/CloudFormation/Master/os-legal-master.yml
@@ -32,14 +32,6 @@ Parameters:
     Type: String
     Default: us-east-1
 
-  ChildTemplateBasePath:
-    Description: >-
-      The base path for where child CloudFormation templates are located – can be relative or absolute, e.g.
-      https://s3.amazonaws.com/dev-os-legal-cloudformation-scripts/Automated/
-    Type: String
-    AllowedPattern: '^https:\/\/s3.amazonaws.com\/.*\/$'
-    Default: https://s3.amazonaws.com/dev-os-legal-cloudformation-scripts/Automated/
-
   LegalConfigS3BucketName:
     Description: The name of the legal service config S3 bucket. Defaults to osdu-legal-config.
     AllowedPattern: "^[a-zA-Z]+[0-9a-zA-Z_-]*$"
@@ -205,6 +197,41 @@ Parameters:
     MinValue: 256
     MaxValue: 131072
 
+  DomainName:
+    Description: >-
+      The optional custom DNS name for the ECS service's load balancer. If omitted, the site will only be accessible
+      via the ECS service's Application Load Balancer DNS name. This value is used in the creation and signing of
+      the service's SSL certificate. Leave blank is not using a custom domain for this deployment.
+    Type: String
+    Default: ''
+
+  HostedZoneName:
+    Description: >-
+      The name of the hosted zone (ex: for storage.osdu.slb.com, this would likely be osdu.slb.com).
+      Leave blank is not using a custom domain for this deployment.
+    Type: String
+    Default: ''
+
+  AcmCertificateArn:
+    Description: >-
+      The Amazon Resource Name (ARN) of an existing AWS Certificate Manager (ACM) certificate.
+      If omitted, a new SSL certified will be requested/generated (only if the custom domain name
+      parameter is provided, otherwise the ECS service's ALB will not use SSL/HTTPS).
+    Type: String
+    AllowedPattern: "^(|arn:aws:acm:.*)$"
+    Default: ''
+
+  VersionNumber:
+    Description: The version number for the service jar being produced
+    Type: String
+    Default: '0.0.1'
+
+  ServiceName:
+    Description: >-
+      The service name associated with the jar package for the Dockerfile.
+    Type: String
+    Default: 'legal'
+
 Resources:
 
   #### Shared Resources ################################################################
@@ -212,7 +239,11 @@ Resources:
   IAMCredentialsStack:
     Type: 'AWS::CloudFormation::Stack'
     Properties:
-      TemplateURL: !Join [ '', [ !Ref ChildTemplateBasePath, iam-credentials.yml ] ]
+      TemplateURL: !Sub
+        - https://s3.amazonaws.com/${CloudFormationS3Bucket}/${ApplicationName}/Automated/${CFNTemplateFilename}
+        - CloudFormationS3Bucket: !ImportValue
+            'Fn::Sub': '${Environment}-S3BucketCloudFormation'
+          CFNTemplateFilename: iam-credentials.yml
       Parameters:
         Environment: !Ref Environment
         Region: !Ref DeploymentRegion
@@ -222,7 +253,11 @@ Resources:
   MessageBusSNSStack:
     Type: 'AWS::CloudFormation::Stack'
     Properties:
-      TemplateURL: !Join [ '', [ !Ref ChildTemplateBasePath, sns-topic.yml ] ]
+      TemplateURL: !Sub
+        - https://s3.amazonaws.com/${CloudFormationS3Bucket}/${ApplicationName}/Automated/${CFNTemplateFilename}
+        - CloudFormationS3Bucket: !ImportValue
+            'Fn::Sub': '${Environment}-S3BucketCloudFormation'
+          CFNTemplateFilename: sns-topic.yml
       Parameters:
         Environment: !Ref Environment
         Region: !Ref DeploymentRegion
@@ -235,18 +270,28 @@ Resources:
     Type: 'AWS::CloudFormation::Stack'
     DependsOn: IAMCredentialsStack
     Properties:
-      TemplateURL: !Join [ '', [ !Ref ChildTemplateBasePath, ecs-network.yml ] ]
+      TemplateURL: !Sub
+        - https://s3.amazonaws.com/${CloudFormationS3Bucket}/${ApplicationName}/Automated/${CFNTemplateFilename}
+        - CloudFormationS3Bucket: !ImportValue
+            'Fn::Sub': '${Environment}-S3BucketCloudFormation'
+          CFNTemplateFilename: ecs-network.yml
       Parameters:
         Environment: !Ref Environment
         Region: !Ref DeploymentRegion
         ApplicationName: !Ref ApplicationName
         ECSPort: !Ref ECSPort
+        DomainName: !Ref DomainName
+        AcmCertificateArn: !Ref AcmCertificateArn
 
   ECSClusterStack:
     Type: 'AWS::CloudFormation::Stack'
     DependsOn: ECSNetworkStack
     Properties:
-      TemplateURL: !Join [ '', [ !Ref ChildTemplateBasePath, ecs-cluster.yml ] ]
+      TemplateURL: !Sub
+        - https://s3.amazonaws.com/${CloudFormationS3Bucket}/${ApplicationName}/Automated/${CFNTemplateFilename}
+        - CloudFormationS3Bucket: !ImportValue
+            'Fn::Sub': '${Environment}-S3BucketCloudFormation'
+          CFNTemplateFilename: ecs-cluster.yml
       Parameters:
         Environment: !Ref Environment
         Region: !Ref DeploymentRegion
@@ -259,13 +304,21 @@ Resources:
         SNSTopicName: !Ref LegalServiceSNSTopicName
         LegalConfigS3BucketName: !Ref LegalConfigS3BucketName
         ECSMemoryAllocation: !Ref ECSMemoryAllocation
+        DomainName: !Ref DomainName
+        HostedZoneName: !Ref HostedZoneName
+        VersionNumber: !Ref VersionNumber
+        ServiceName: !Ref ServiceName
 
   #### Legal Repository DB #############################################################
 
   LegalRepositoryStack:
     Type: 'AWS::CloudFormation::Stack'
     Properties:
-      TemplateURL: !Join [ '', [ !Ref ChildTemplateBasePath, legal-repository.yml ] ]
+      TemplateURL: !Sub
+        - https://s3.amazonaws.com/${CloudFormationS3Bucket}/${ApplicationName}/Automated/${CFNTemplateFilename}
+        - CloudFormationS3Bucket: !ImportValue
+            'Fn::Sub': '${Environment}-S3BucketCloudFormation'
+          CFNTemplateFilename: legal-repository.yml
       Parameters:
         Environment: !Ref Environment
         Region: !Ref DeploymentRegion
@@ -277,7 +330,11 @@ Resources:
     Type: 'AWS::CloudFormation::Stack'
     DependsOn: IAMCredentialsStack
     Properties:
-      TemplateURL: !Join [ '', [ !Ref ChildTemplateBasePath, config-bucket.yml ] ]
+      TemplateURL: !Sub
+        - https://s3.amazonaws.com/${CloudFormationS3Bucket}/${ApplicationName}/Automated/${CFNTemplateFilename}
+        - CloudFormationS3Bucket: !ImportValue
+            'Fn::Sub': '${Environment}-S3BucketCloudFormation'
+          CFNTemplateFilename: config-bucket.yml
       Parameters:
         Environment: !Ref Environment
         Region: !Ref DeploymentRegion
diff --git a/provider/legal-aws/CloudFormation/Params/dev.template_configuration.json b/provider/legal-aws/CloudFormation/Params/dev.template_configuration.json
index a417686f5..d580c998f 100644
--- a/provider/legal-aws/CloudFormation/Params/dev.template_configuration.json
+++ b/provider/legal-aws/CloudFormation/Params/dev.template_configuration.json
@@ -2,7 +2,6 @@
   "Parameters" : {
     "Environment" : "dev",
     "DeploymentRegion" : "us-east-1",
-    "ChildTemplateBasePath" : "https://s3.amazonaws.com/dev-os-legal-cloudformation-scripts/Automated/",
     "ApplicationName" : "os-legal",
     "KeyName": "legal-ecs-keypair",
     "DesiredCapacity": "1",
@@ -15,9 +14,14 @@
     "LegalServiceIamKeyRotationSerial": "1",
     "LegalServiceSNSTopicName": "osdu-legal-messages",
     "LegalServiceSQSQueueName": "osdu-legal-queue",
-    "ECSPort": "80",
+    "ECSPort": "443",
     "ECSCPUAllocation": "1024",
-    "ECSMemoryAllocation": "3072"
+    "ECSMemoryAllocation": "3072",
+    "DomainName": "",
+    "HostedZoneName": "",
+    "AcmCertificateArn": "",
+    "ServiceName": "legal",
+    "VersionNumber": "0.0.5-SNAPSHOT"
   },
   "Tags" : {
     "Environment" : "dev"
diff --git a/provider/legal-aws/CloudFormation/Params/prod.template_configuration.json b/provider/legal-aws/CloudFormation/Params/prod.template_configuration.json
index c7a512be4..81c36ab18 100644
--- a/provider/legal-aws/CloudFormation/Params/prod.template_configuration.json
+++ b/provider/legal-aws/CloudFormation/Params/prod.template_configuration.json
@@ -2,7 +2,6 @@
   "Parameters" : {
     "Environment" : "prod",
     "DeploymentRegion" : "us-east-1",
-    "ChildTemplateBasePath" : "https://s3.amazonaws.com/prod-os-legal-cloudformation-scripts/Automated/",
     "ApplicationName" : "os-legal",
     "KeyName": "legal-ecs-keypair",
     "DesiredCapacity": "1",
@@ -13,9 +12,14 @@
     "DataStorageS3BucketName": "osdu-legal-config",
     "LegalServiceIamUsername": "service-user-os-legal",
     "LegalServiceIamKeyRotationSerial": "1",
-    "ECSPort": "80",
+    "ECSPort": "443",
     "ECSCPUAllocation": "1024",
-    "ECSMemoryAllocation": "3072"
+    "ECSMemoryAllocation": "3072",
+    "DomainName": "",
+    "HostedZoneName": "",
+    "AcmCertificateArn": "",
+    "ServiceName": "legal",
+    "VersionNumber": "0.0.5-SNAPSHOT"
   },
   "Tags" : {
     "Environment" : "prod"
diff --git a/provider/legal-aws/CloudFormation/Params/uat.template_configuration.json b/provider/legal-aws/CloudFormation/Params/uat.template_configuration.json
index afd6e8fb1..6d83e64ed 100644
--- a/provider/legal-aws/CloudFormation/Params/uat.template_configuration.json
+++ b/provider/legal-aws/CloudFormation/Params/uat.template_configuration.json
@@ -2,7 +2,6 @@
   "Parameters" : {
     "Environment" : "uat",
     "DeploymentRegion" : "us-east-1",
-    "ChildTemplateBasePath" : "https://s3.amazonaws.com/uat-os-legal-cloudformation-scripts/Automated/",
     "ApplicationName" : "os-legal",
     "KeyName": "legal-ecs-keypair",
     "DesiredCapacity": "1",
@@ -13,9 +12,14 @@
     "DataStorageS3BucketName": "osdu-legal-config",
     "LegalServiceIamUsername": "service-user-os-legal",
     "LegalServiceIamKeyRotationSerial": "1",
-    "ECSPort": "80",
+    "ECSPort": "443",
     "ECSCPUAllocation": "1024",
-    "ECSMemoryAllocation": "3072"
+    "ECSMemoryAllocation": "3072",
+    "DomainName": "",
+    "HostedZoneName": "",
+    "AcmCertificateArn": "",
+    "ServiceName": "legal",
+    "VersionNumber": "0.0.5-SNAPSHOT"
   },
   "Tags" : {
     "Environment" : "uat"
diff --git a/provider/legal-aws/Dockerfile b/provider/legal-aws/Dockerfile
index 6e108d41e..97892b25c 100644
--- a/provider/legal-aws/Dockerfile
+++ b/provider/legal-aws/Dockerfile
@@ -13,11 +13,14 @@
 # limitations under the License.
 FROM amazoncorretto:8
 
-ARG JAR_VERSION
-ENV JAR_FILE=legal-aws-${JAR_VERSION}-spring-boot.jar
+ARG versionNumber
+
+ARG service
+ENV serviceName=${service}-aws
+ENV awsJar=${serviceName}-${versionNumber}-spring-boot.jar
 
 WORKDIR /
-COPY provider/legal-aws/target/$JAR_FILE $JAR_FILE
+COPY provider/${serviceName}/target/${awsJar} ${awsJar}
 EXPOSE 8080
 
-CMD java -jar $JAR_FILE
+CMD ["sh","-c", " java -jar ${awsJar}"]
\ No newline at end of file
diff --git a/provider/legal-aws/buildspec-jar-deploy.yml b/provider/legal-aws/buildspec-jar-deploy.yml
new file mode 100644
index 000000000..df2329eca
--- /dev/null
+++ b/provider/legal-aws/buildspec-jar-deploy.yml
@@ -0,0 +1,64 @@
+# Copyright © Amazon Web Services
+#
+# 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
+#
+#      http://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.
+
+version: 0.2
+
+phases:
+  install:
+    runtime-versions:
+      java: openjdk8
+    commands:
+      - echo Entered the install phase...
+      - apt-get update -y
+      - apt-get install -y maven
+      - java -version
+      - mvn clean # .m2 is not created until the first Maven command
+      - cp ./provider/legal-aws/maven/settings.xml /root/.m2/settings.xml # copy the AWS-specific settings.xml to the CodeBuild instance's .m2 folder
+      - cat /root/.m2/settings.xml
+      - java -version
+      - export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
+      - echo $JAVA_HOME
+      - mvn -version
+      - echo "Look below for M2 bucket name:"
+      - echo $M2_REPO_S3_BUCKET
+      - aws s3 sync s3://$M2_REPO_S3_BUCKET /root/.m2 # copy previous state of the shared libraries' .m2 folder from S3 to local
+      - nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2& # start the Docker Daemon
+      - timeout 15 sh -c "until docker info; do echo .; sleep 1; done" # wait for Docker to be ready before proceeding to the build steps
+  build:
+    commands:
+      - echo os-legal Java build started on `date`...
+      - java -version
+      - export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
+      - mvn -version
+      - echo All environment variables
+      - printenv
+      - mvn clean test -e -pl legal-core,provider/legal-aws
+      - echo ...os-legal Java build completed on `date`.
+      - echo os-legal beginning packaging to jar...
+      # NOTE: we have to be extremely specific in this command, both with the profiles AND the modules to do two things:
+      # (1) ensure that the core is explicitly built first, so that it doesn't look for artifacts that may not exist yet on
+      # ADO in order to build the AWS provider module, and (2) exclude GCP so that it doesn't fail due to errors that the
+      # GCP resources don't exist (we aren't building them on AWS, so they definitely are 'missing', and are missing
+      # dependencies for the GCP tests to pass, anyway).
+      - mvn clean install '-Plegal-aws,!legal-gcp' -pl legal-core,provider/legal-aws -Ddeployment.environment=$ENVIRONMENT
+      - echo Uploading os-legal JAR to S3...
+      - aws s3 cp provider/$JAR_SERVICE_BASE-aws/target s3://$JAR_DEPLOY_S3_BUCKET/$JAR_SERVICE_BASE-aws --recursive --exclude "*" --include "*.jar" # build and push the JAR(s) to S3
+
+cache:
+  paths:
+    - '/root/.m2/**/*'
+
+artifacts:
+  files:
+    - '**/*'
diff --git a/provider/legal-aws/buildspec-post-deploy.yml b/provider/legal-aws/buildspec-post-deploy.yml
index a137cc3ad..7a8c6b68e 100644
--- a/provider/legal-aws/buildspec-post-deploy.yml
+++ b/provider/legal-aws/buildspec-post-deploy.yml
@@ -30,6 +30,9 @@ phases:
       - export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
       - echo $JAVA_HOME
       - mvn -version
+      - echo "Look below for M2 bucket name:"
+      - echo $M2_REPO_S3_BUCKET
+      - aws s3 sync s3://$M2_REPO_S3_BUCKET /root/.m2 # copy previous state of the shared libraries' .m2 folder from S3 to local
       - nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2& # start the Docker Daemon
       - timeout 15 sh -c "until docker info; do echo .; sleep 1; done" # wait for Docker to be ready before proceeding to the build steps
   pre_build:
@@ -45,21 +48,26 @@ phases:
       - java -version
       - export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
       - mvn -version
-      - echo Setting environment variables from CloudFormation Exports... # use the AWS CLI commands to query for the CloudFormation export values created in the previous step and set the required environment variables
+      - echo Setting variables from CloudFormation Exports... # use the AWS CLI commands to query for the CloudFormation export values created in the previous step and set the required environment variables
       - echo Environment - $ENVIRONMENT
       - echo AWSRegion - $AWS_REGION
       - echo AWSAccountID - $AWS_ACCOUNT_ID
-      - export SNS_TOPIC_NAME=$(aws cloudformation list-exports --query "Exports[?Name=='$ENVIRONMENT-OSDUStorageSNSTopic'].[Value]" --output text --region $AWS_REGION)
-      - export S3_LEGAL_CONFIG_BUCKET=$(aws cloudformation list-exports --query "Exports[?Name=='$ENVIRONMENT-S3BucketLegalConfig'].[Value]" --output text --region $AWS_REGION)
+      - export VERSIONNUMBER=$(aws cloudformation list-exports --query "Exports[?Name=='$ENVIRONMENT-$APPLICATION_NAME-JarVersionNumber'].[Value]" --output text --region $AWS_REGION)
+      - export SERVICE=$(aws cloudformation list-exports --query "Exports[?Name=='$ENVIRONMENT-$APPLICATION_NAME-JarServiceName'].[Value]" --output text --region $AWS_REGION)
       - echo ...finished setting environment variables!
       - echo All environment variables
       - printenv
-      - mvn clean test -e -pl legal-core,provider/legal-aws -Ddeployment.environment=$ENVIRONMENT -Daws.accessKeyId=$AWS_SECRET_KEY -Daws.secretKey=$AWS_ACCESS_KEY_ID -Dazure.devops.token=$VSTS_FEED_TOKEN -Dazure.devops.username=$VSTS_FEED_USER -DSNS_TOPIC_NAME=$SNS_TOPIC_NAME -DS3_LEGAL_CONFIG_BUCKET=$S3_LEGAL_CONFIG_BUCKET -DAWS_ACCOUNT_ID=$AWS_ACCOUNT_ID -DAWS_REGION=$AWS_REGION -DaltSnapshotDeploymentRepository=snapshot::default::file:../../local-snapshots-dir -DaltReleaseDeploymentRepository=release::default::file:../../local-release-dir -DaltDeploymentRepository=release::default::file:../../local-release-dir
+      - mvn clean test -pl legal-core,provider/legal-aws
       - echo ...os-legal Java build completed on `date`.
       - echo os-legal beginning packaging to jar...
-      - mvn clean deploy -e -pl legal-core,provider/legal-aws -Ddeployment.environment=$ENVIRONMENT -Dazure.devops.token=$VSTS_FEED_TOKEN -Dazure.devops.username=$VSTS_FEED_USER -DaltSnapshotDeploymentRepository=snapshot::default::file:../../local-snapshots-dir -DaltReleaseDeploymentRepository=release::default::file:../../local-release-dir -DaltDeploymentRepository=release::default::file:../../local-release-dir
+      # NOTE: we have to be extremely specific in this command, both with the profiles AND the modules to do two things:
+      # (1) ensure that the core is explicitly built first, so that it doesn't look for artifacts that may not exist yet on
+      # ADO in order to build the AWS provider module, and (2) exclude GCP so that it doesn't fail due to errors that the
+      # GCP resources don't exist (we aren't building them on AWS, so they definitely are 'missing', and are missing
+      # dependencies for the GCP tests to pass, anyway).
+      - mvn clean install '-Plegal-aws,!legal-gcp' -pl legal-core,provider/legal-aws -Dversion.number=$VERSIONNUMBER -Ddeployment.environment=$ENVIRONMENT
       - echo os-legal Docker image build started on `date`...
-      - docker build -f provider/legal-aws/Dockerfile -t $REPOSITORY_URI:latest .
+      - docker build -f provider/legal-aws/Dockerfile -t $REPOSITORY_URI:latest --build-arg versionNumber=$VERSIONNUMBER --build-arg service=$SERVICE .
       - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
       - echo ...os-legal Docker image build completed on `date`.
       - echo Pushing the Docker image to ECR...
diff --git a/provider/legal-aws/buildspec-pre-deploy.yml b/provider/legal-aws/buildspec-pre-deploy.yml
index 5fd7313b1..2e9b33ffe 100644
--- a/provider/legal-aws/buildspec-pre-deploy.yml
+++ b/provider/legal-aws/buildspec-pre-deploy.yml
@@ -17,12 +17,11 @@ version: 0.1
 phases:
   build:
     commands:
-      - echo Starting 'Copying CloudFormation scripts to S3://$CFN_S3_BUCKET'
-#      - cd provider/legal-aws
+      - echo Starting 'Copying CloudFormation scripts to S3://$CFN_S3_BUCKET/$APPLICATION_NAME'
       - pwd
       - ls
-      - aws s3 cp ./provider/legal-aws/CloudFormation "s3://$CFN_S3_BUCKET" --exclude "*" --include "*.yml" --recursive --debug
-      - echo Ending 'Ending CloudFormation scripts to S3://$CFN_S3_BUCKET'
+      - aws s3 cp ./provider/legal-aws/CloudFormation "s3://$CFN_S3_BUCKET/$APPLICATION_NAME" --exclude "*" --include "*.yml" --recursive --debug
+      - echo Ending 'Ending CloudFormation scripts to S3://$CFN_S3_BUCKET/$APPLICATION_NAME'
 
 artifacts:
   files:
diff --git a/provider/legal-aws/pom.xml b/provider/legal-aws/pom.xml
index 1ac763823..97bac0d43 100644
--- a/provider/legal-aws/pom.xml
+++ b/provider/legal-aws/pom.xml
@@ -27,19 +27,19 @@
 
     <artifactId>legal-aws</artifactId>
     <packaging>jar</packaging>
-    <version>0.0.5-SNAPSHOT</version>
 
     <properties>
-        <aws.version>1.11.637</aws.version>
+        <aws.version>1.11.651</aws.version>
         <deployment.environment>dev</deployment.environment>
+        <version.number>0.0.5-SNAPSHOT</version.number>
     </properties>
 
     <dependencies>
         <!-- Internal packages -->
         <dependency>
             <groupId>org.opengroup.osdu.core.aws</groupId>
-            <artifactId>aws-osdu-util</artifactId>
-            <version>0.0.9</version>
+            <artifactId>os-core-lib-aws</artifactId>
+            <version>0.0.10</version>
         </dependency>
         <dependency>
             <groupId>org.opengroup.osdu</groupId>
@@ -49,30 +49,30 @@
         <dependency>
             <groupId>org.opengroup.osdu.legal</groupId>
             <artifactId>legal-core</artifactId>
-            <version>0.0.5-SNAPSHOT</version>
+            <version>${version.number}</version>
         </dependency>
 
         <!-- AWS-managed packages -->
         <dependency>
             <groupId>com.amazonaws</groupId>
             <artifactId>aws-java-sdk-bom</artifactId>
-            <version>1.11.651</version>
+            <version>${aws.version}</version>
             <type>pom</type>
         </dependency>
         <dependency>
             <groupId>com.amazonaws</groupId>
             <artifactId>aws-java-sdk</artifactId>
-            <version>1.11.651</version>
+            <version>${aws.version}</version>
         </dependency>
         <dependency>
             <groupId>com.amazonaws</groupId>
             <artifactId>aws-java-sdk-core</artifactId>
-            <version>1.11.651</version>
+            <version>${aws.version}</version>
         </dependency>
         <dependency>
             <groupId>com.amazonaws</groupId>
             <artifactId>aws-java-sdk-dynamodb</artifactId>
-            <version>1.11.651</version>
+            <version>${aws.version}</version>
         </dependency>
 
         <!-- Third party Apache 2.0 license packages -->
@@ -116,15 +116,22 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-all</artifactId>
-            <version>1.10.19</version>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <version>2.0.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.powermock</groupId>
             <artifactId>powermock-module-junit4</artifactId>
             <version>2.0.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.0.0</version>
+            <scope>test</scope>
         </dependency>
     </dependencies>
 
@@ -177,4 +184,43 @@
         </plugins>
     </build>
 
+    <profiles>
+        <profile>
+            <id>aws-dev</id>
+            <activation>
+                <property>
+                    <name>deployment.environment</name>
+                    <value>dev</value>
+                </property>
+            </activation>
+            <properties>
+                <aws.version>1.11.651</aws.version>
+            </properties>
+        </profile>
+        <profile>
+            <id>aws-uat</id>
+            <activation>
+                <property>
+                    <name>deployment.environment</name>
+                    <value>uat</value>
+                </property>
+            </activation>
+            <properties>
+                <aws.version>1.11.632</aws.version>
+            </properties>
+        </profile>
+        <profile>
+            <id>aws-prod</id>
+            <activation>
+                <property>
+                    <name>deployment.environment</name>
+                    <value>prod</value>
+                </property>
+            </activation>
+            <properties>
+                <aws.version>1.11.632</aws.version>
+            </properties>
+        </profile>
+    </profiles>
+
 </project>
\ No newline at end of file
diff --git a/provider/legal-aws/src/main/java/org/opengroup/osdu/legal/aws/tags/dataaccess/LegalTagRepositoryImpl.java b/provider/legal-aws/src/main/java/org/opengroup/osdu/legal/aws/tags/dataaccess/LegalTagRepositoryImpl.java
index 468275f44..bc5d06522 100644
--- a/provider/legal-aws/src/main/java/org/opengroup/osdu/legal/aws/tags/dataaccess/LegalTagRepositoryImpl.java
+++ b/provider/legal-aws/src/main/java/org/opengroup/osdu/legal/aws/tags/dataaccess/LegalTagRepositoryImpl.java
@@ -19,9 +19,9 @@ import org.opengroup.osdu.core.aws.dynamodb.DynamoDBQueryHelper;
 import org.opengroup.osdu.core.aws.dynamodb.QueryPageResult;
 import org.opengroup.osdu.core.common.model.legal.ListLegalTagArgs;
 import org.opengroup.osdu.core.common.model.legal.LegalTag;
-import org.opengroup.osdu.legal.provider.interfaces.ILegalTagRepository;
 
 import org.opengroup.osdu.core.common.model.http.AppException;
+import org.opengroup.osdu.legal.provider.interfaces.ILegalTagRepository;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Repository;
 
diff --git a/provider/legal-aws/src/main/resources/application.properties b/provider/legal-aws/src/main/resources/application.properties
index 37fafb4a3..62ec9ab34 100644
--- a/provider/legal-aws/src/main/resources/application.properties
+++ b/provider/legal-aws/src/main/resources/application.properties
@@ -6,13 +6,9 @@ server.port=${APPLICATION_PORT}
 JAVA_HEAP_OPTS=-Xms${JAVA_HEAP_MEMORY}M -Xmx${JAVA_HEAP_MEMORY}M
 JAVA_GC_OPTS=-XX:+UseG1GC -XX:+UseStringDeduplication -XX:InitiatingHeapOccupancyPercent=45
 
-## Spring Configuration
-spring.security.user.name=opendes@byoc.local
-spring.security.user.password=123
-spring.security.user.roles=service.legal.admin
 
 ## AWS Lambda configuration
-aws.lambda.get-groups-function-name=dev-os-entitlements-GroupsFunction-1DTM5841SUIFO
+aws.lambda.get-groups-function-name=${ENVIRONMENT}-os-entitlements-GroupsFunction
 
 REGION=${AWS_REGION}
 AUTHORIZE_API=notused
@@ -28,7 +24,7 @@ aws.dynamodb.endpoint=dynamodb.${AWS_REGION}.amazonaws.com
 ## AWS S3 configuration
 aws.s3.region=${AWS_REGION}
 aws.s3.endpoint=s3.${AWS_REGION}.amazonaws.com
-aws.s3.legal.config.bucket-name=${ENVIRONMENT}-${S3_LEGAL_CONFIG_BUCKET}
+aws.s3.legal.config.bucket-name=${S3_LEGAL_CONFIG_BUCKET}
 aws.s3.legal.config.file-name=Legal_COO.json
 
 ## AWS SNS configuration
diff --git a/provider/legal-aws/src/test/java/org/opengroup/osdu/legal/aws/api/LegalTagRepositoryImplTest.java b/provider/legal-aws/src/test/java/org/opengroup/osdu/legal/aws/api/LegalTagRepositoryImplTest.java
index d6e4ca7f8..6b59e01e8 100644
--- a/provider/legal-aws/src/test/java/org/opengroup/osdu/legal/aws/api/LegalTagRepositoryImplTest.java
+++ b/provider/legal-aws/src/test/java/org/opengroup/osdu/legal/aws/api/LegalTagRepositoryImplTest.java
@@ -20,6 +20,7 @@ import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.*;
+import org.mockito.runners.MockitoJUnitRunner;
 import org.opengroup.osdu.core.aws.dynamodb.DynamoDBQueryHelper;
 import org.opengroup.osdu.core.aws.dynamodb.QueryPageResult;
 import org.opengroup.osdu.legal.aws.tags.dataaccess.LegalDoc;
@@ -27,7 +28,6 @@ import org.opengroup.osdu.legal.aws.tags.dataaccess.LegalTagRepositoryImpl;
 import org.opengroup.osdu.core.common.model.legal.ListLegalTagArgs;
 import org.opengroup.osdu.core.common.model.legal.LegalTag;
 import org.springframework.boot.test.context.SpringBootTest;
-import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.UnsupportedEncodingException;
 import java.util.*;
diff --git a/provider/legal-ibm/README.md b/provider/legal-ibm/README.md
new file mode 100644
index 000000000..2fba562be
--- /dev/null
+++ b/provider/legal-ibm/README.md
@@ -0,0 +1,43 @@
+# IBM's backend for legal
+
+## A note about authentication and entitlements
+
+OAuth2 JWT token authentication is now enforced
+
+## A note about publishing legal tag changes
+
+This is not implemented yet, the Bean that takes the pubsub requests is a Mock.
+However, since we are using cloudant, we could keep it that way and leverage
+cloudant's change stream
+
+## Testing
+
+For testing a Cloudant instance is needed. Once you have a cloudant instance
+in IBMCloud, download the credentials JSON somewhere, and point the IBM_CREDENTIALS_FILE
+environment variable to it:
+
+    $> export IBM_CREDENTIALS_FILE=/path/to/credentials.json
+
+
+### Running the unit tests
+
+To run the unit tests, go to `provider/legal-ibm` and run:
+
+    $> mvn test
+
+### Running the acceptance tests
+
+For this it ideal to open another terminal with the IBM_CREDENTIALS_FILE variable set also.
+
+In one terminal go to `testing/legal-test-ibm` and run:
+
+    $> setup_acceptance.sh
+    $> run_service.sh
+
+In the other, also go to `testing/legal-test-ibm` and run: 
+
+    $> run_tests.sh
+
+To delete the test databases, just run:
+
+    $> teardown_acceptance.sh
diff --git a/provider/legal-ibm/pom.xml b/provider/legal-ibm/pom.xml
new file mode 100644
index 000000000..83b71cf9f
--- /dev/null
+++ b/provider/legal-ibm/pom.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<parent>
+		<artifactId>legal-service</artifactId>
+		<groupId>org.opengroup.osdu.legal</groupId>
+		<version>0.0.5-SNAPSHOT</version>
+		<relativePath>../../pom.xml</relativePath>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>legal-ibm</artifactId>
+	<packaging>jar</packaging>
+	<properties>
+		<osdu.ibmcore.version>0.0.13-SNAPSHOT</osdu.ibmcore.version>
+	</properties>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.opengroup.osdu.legal</groupId>
+			<artifactId>legal-core</artifactId>
+			<version>0.0.5-SNAPSHOT</version>
+		</dependency>
+		<dependency>
+			<groupId>org.opengroup.osdu</groupId>
+			<artifactId>os-core-lib-ibm</artifactId>
+			<version>${osdu.ibmcore.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+		</dependency>
+
+		<!-- Test Dependencies -->
+		<!-- https://mvnrepository.com/artifact/org.powermock/powermock-api-mockito2 -->
+		<dependency>
+			<groupId>org.powermock</groupId>
+			<artifactId>powermock-api-mockito2</artifactId>
+			<version>2.0.2</version>
+			<scope>test</scope>
+		</dependency>
+		<!-- https://mvnrepository.com/artifact/org.powermock/powermock-module-junit4 -->
+		<dependency>
+			<groupId>org.powermock</groupId>
+			<artifactId>powermock-module-junit4</artifactId>
+			<version>2.0.2</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-core</artifactId>
+			<version>3.0.0</version>
+			<scope>test</scope>
+		</dependency>
+
+		<!-- To configure logback with groovy -->
+		<dependency>
+			<groupId>org.codehaus.groovy</groupId>
+			<artifactId>groovy</artifactId>
+			<version>2.5.8</version>
+			<type>pom</type>
+		</dependency>
+
+		<dependency>
+			<groupId>org.codehaus.groovy</groupId>
+			<artifactId>groovy-jsr223</artifactId>
+			<version>2.5.8</version>
+			<type>pom</type>
+		</dependency>
+		
+		<dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+				<executions>
+					<execution>
+						<goals>
+							<goal>repackage</goal>
+						</goals>
+						<configuration>
+							<classifier>spring-boot</classifier>
+							<mainClass>
+								org.opengroup.osdu.legal.ibm.LegalApplication
+							</mainClass>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>
diff --git a/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/LegalApplication.java b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/LegalApplication.java
new file mode 100644
index 000000000..65edb1f99
--- /dev/null
+++ b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/LegalApplication.java
@@ -0,0 +1,29 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@ComponentScan("org.opengroup.osdu")
+@SpringBootApplication
+public class LegalApplication {
+	
+	public static void main(String[] args) {
+		SpringApplication.run(LegalApplication.class, args);
+	}
+}
diff --git a/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/countries/StorageReaderFactoryImpl.java b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/countries/StorageReaderFactoryImpl.java
new file mode 100644
index 000000000..4780610af
--- /dev/null
+++ b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/countries/StorageReaderFactoryImpl.java
@@ -0,0 +1,110 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.countries;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.inject.Inject;
+
+import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
+import org.opengroup.osdu.core.common.model.http.AppException;
+import org.opengroup.osdu.core.common.model.tenant.TenantInfo;
+import org.opengroup.osdu.core.ibm.auth.ServiceCredentials;
+import org.opengroup.osdu.core.ibm.cloudant.IBMCloudantClientFactory;
+import org.opengroup.osdu.legal.provider.interfaces.IStorageReader;
+import org.opengroup.osdu.legal.provider.interfaces.IStorageReaderFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author mbayser
+ *
+ */
+@Component
+public class StorageReaderFactoryImpl implements IStorageReaderFactory {
+	
+	private ConcurrentHashMap<String, IStorageReader> readers = new ConcurrentHashMap<>();
+	
+	@Value("${ibm.legal.db.url}") 
+	private String dbUrl;
+	@Value("${ibm.legal.db.apikey:#{null}}")
+	private String apiKey;
+	@Value("${ibm.legal.db.user:#{null}}")
+	private String dbUser;
+	@Value("${ibm.legal.db.password:#{null}}")
+	private String dbPassword;
+	@Value("${ibm.env.prefix:local-dev}")
+	private String dbNamePrefix;
+	
+	@Value("${ibm.legal.db.credentials:#{null}}")
+	private String credentialsJSON;
+	
+	@Value("${ibm.countries.db.name:countries}")
+	private String dbName;
+	
+	@Inject
+	private ResourceLoader resourceLoader;
+	
+	@Inject
+	private JaxRsDpsLog logger;
+	
+	
+	/* (non-Javadoc)
+	 * @see org.opengroup.osdu.legal.countries.StorageReaderFactory#getReader(org.opengroup.osdu.core.multitenancy.TenantInfo, java.lang.String)
+	 */
+	@Override
+	
+	public IStorageReader getReader(TenantInfo tenant, String projectRegion) {
+		
+			ServiceCredentials creds = null;
+			
+			if (dbUrl != null && apiKey != null) {
+				creds = new ServiceCredentials(dbUrl, apiKey);
+			} else if (dbUrl != null && dbUser != null) {
+				creds = new ServiceCredentials(dbUrl, dbUser, dbPassword);
+			} else {
+				try {
+					creds = new ServiceCredentials(new InputStreamReader(resourceLoader.getResource(credentialsJSON).getInputStream()));
+				} catch (IOException e) {
+					logger.error(" 500, Malformed URL Invalid cloudant URL", e);
+					throw new AppException(500, "Malformed URL", "Invalid cloudant URL", e);
+				}
+			} 
+				
+			IBMCloudantClientFactory cloudantFactory = new IBMCloudantClientFactory(creds);
+
+			return getReader(cloudantFactory, tenant, projectRegion, dbNamePrefix, dbName);		
+	}
+	
+	public IStorageReader getReader(IBMCloudantClientFactory cloudantFactory, TenantInfo tenant, String projectRegion, String dbNamePrefix, String dbName) {
+
+		final String key = tenant+":"+projectRegion;
+		readers.computeIfAbsent(key, s -> {
+			try {		
+				return new StorageReaderImpl(tenant, projectRegion, cloudantFactory, dbNamePrefix, dbName);
+			} catch (MalformedURLException | FileNotFoundException e) {
+				logger.error("Error creating a Storage Reader", e);
+				return null;
+			}
+		});
+		return readers.get(key);		
+	}
+}
\ No newline at end of file
diff --git a/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/countries/StorageReaderImpl.java b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/countries/StorageReaderImpl.java
new file mode 100644
index 000000000..e85b1cee8
--- /dev/null
+++ b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/countries/StorageReaderImpl.java
@@ -0,0 +1,111 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.countries;
+
+import static com.cloudant.client.api.query.Expression.eq;
+import static com.cloudant.client.api.query.Operation.and;
+
+import java.io.FileNotFoundException;
+import java.lang.ref.WeakReference;
+import java.net.MalformedURLException;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.inject.Inject;
+
+import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
+import org.opengroup.osdu.core.common.model.http.AppException;
+import org.opengroup.osdu.core.common.model.tenant.TenantInfo;
+import org.opengroup.osdu.core.ibm.cloudant.IBMCloudantClientFactory;
+import org.opengroup.osdu.legal.provider.interfaces.IStorageReader;
+
+import com.cloudant.client.api.CloudantClient;
+import com.cloudant.client.api.Database;
+import com.cloudant.client.api.query.QueryBuilder;
+import com.cloudant.client.api.query.QueryResult;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+
+/**
+ * @author mbayser
+ *
+ */
+public class StorageReaderImpl implements IStorageReader {
+	
+    private final TenantInfo tenantInfo;
+    private final String cloudRegion;
+    private final String dbNamePrefix;
+    private final String dbName;
+    private final IBMCloudantClientFactory cloudantFactory;
+    private final CloudantClient cloudant;
+    private static final long CACHE_EXPIRATION_MS = 60000;
+    private AtomicLong lastUpdate = new AtomicLong(0);
+    private WeakReference<byte[]> cache = new WeakReference<byte[]>(null);
+     
+
+    public StorageReaderImpl(TenantInfo tenantInfo, String projectRegion, IBMCloudantClientFactory cloudantFactory, String dbNamePrefix, String dbName) throws MalformedURLException, FileNotFoundException {
+        this.tenantInfo = tenantInfo;
+        this.cloudRegion = projectRegion;
+        this.dbNamePrefix =  dbNamePrefix;
+        this.dbName = dbName;
+        this.cloudantFactory = cloudantFactory;
+        this.cloudant = cloudantFactory.getClient();
+    }
+    
+    @Inject
+	private JaxRsDpsLog logger;
+
+	/* (non-Javadoc)
+	 * @see org.opengroup.osdu.legal.countries.StorageReader#readAllBytes()
+	 */
+	@Override
+	public byte[] readAllBytes() {
+		 //There is a race condition here, but other than a few wasteful HTTP requests it does no harm.
+		// A mutex here on the other hand could lead to increased latency.
+		if ((lastUpdate.get() + CACHE_EXPIRATION_MS) > System.currentTimeMillis()) {
+			byte[] c = cache.get();
+			if (c != null) {
+				return c;
+			}
+		}
+		
+		try {
+			
+			Database db = cloudantFactory.getDatabase(cloudant, dbNamePrefix, dbName);
+	        
+	        QueryResult<JsonObject> result = db.query(new QueryBuilder(and(eq("tenant", tenantInfo.getName()), eq("region", cloudRegion)))
+	        		.fields("name", "alpha2", "numeric", "residencyRisk", "typesNotApplyDataResidency").build(), JsonObject.class);
+	        
+	        // The encapsulation of the Database class sucks in this case. If we could grab its internal
+	        // import com.cloudant.client.org.lightcouch.CouchDbClient we could avoid de-serializing the
+	        // response just to serialize it again.
+	        JsonArray array = new JsonArray();
+	        for (JsonObject s: result.getDocs()) { 
+	        	array.add(s);
+	        }
+	        byte[] blob = cloudant.getGson().toJson(array).getBytes();
+	        
+	        cache = new WeakReference<byte[]>(blob);
+	        lastUpdate.set(System.currentTimeMillis());
+	        
+	        return blob;
+		} catch (MalformedURLException e) {
+		    logger.error(" 500, Malformed URL Invalid cloudant URL", e);
+			throw new AppException(500, "Malformed URL", "Invalid cloudant URL", e);
+		}
+	}
+
+}
diff --git a/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/di/DevNullPublisher.java b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/di/DevNullPublisher.java
new file mode 100644
index 000000000..ab4ca44b3
--- /dev/null
+++ b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/di/DevNullPublisher.java
@@ -0,0 +1,46 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.di;
+
+
+import org.opengroup.osdu.core.common.model.http.DpsHeaders;
+import org.opengroup.osdu.core.common.model.legal.StatusChangedTags;
+import org.opengroup.osdu.legal.provider.interfaces.ILegalTagPublisher;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author mbayser
+ *
+ */
+@ConditionalOnProperty(
+	    value="ibm.legal.publisher.devnull", 
+	    havingValue = "true", 
+	    matchIfMissing = false)
+@Service
+public class DevNullPublisher implements ILegalTagPublisher {
+
+
+	/* (non-Javadoc)
+	 * @see org.opengroup.osdu.legal.provider.interfaces.LegalTagPublisher#publish(java.lang.String, org.opengroup.osdu.core.api.DpsHeaders, org.opengroup.osdu.legal.jobs.StatusChangedTags)
+	 */
+	@Override
+	public void publish(String projectId, DpsHeaders headers, StatusChangedTags tags) throws Exception {
+		// TODO Auto-generated method stub
+
+	}
+
+}
diff --git a/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/di/DpsLogFactory.java b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/di/DpsLogFactory.java
new file mode 100644
index 000000000..8123e25d8
--- /dev/null
+++ b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/di/DpsLogFactory.java
@@ -0,0 +1,41 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.di;
+
+import org.opengroup.osdu.core.common.logging.ILogger;
+import org.opengroup.osdu.core.ibm.logging.logger.IBMLoggingProvider;
+import org.springframework.beans.factory.config.AbstractFactoryBean;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author mbayser
+ *
+ */
+//@Component
+public class DpsLogFactory extends AbstractFactoryBean<ILogger> {
+
+	private IBMLoggingProvider ibmLoggingProvider = new IBMLoggingProvider();
+
+	@Override
+	protected ILogger createInstance() throws Exception {
+		return ibmLoggingProvider.getLogger();
+	}
+
+	@Override
+	public Class<?> getObjectType() {
+		return ILogger.class;
+	}
+}
\ No newline at end of file
diff --git a/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/jobs/LegalTagPublisherImpl.java b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/jobs/LegalTagPublisherImpl.java
new file mode 100644
index 000000000..a9e143e73
--- /dev/null
+++ b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/jobs/LegalTagPublisherImpl.java
@@ -0,0 +1,65 @@
+//  Copyright 2020 IBM Corp. All Rights Reserved
+//
+//  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
+//
+//       http://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.legal.ibm.jobs;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.opengroup.osdu.core.common.model.http.DpsHeaders;
+import org.opengroup.osdu.core.common.model.legal.StatusChangedTag;
+import org.opengroup.osdu.core.common.model.legal.StatusChangedTags;
+import org.opengroup.osdu.core.ibm.messagebus.IMessageFactory;
+import org.opengroup.osdu.legal.provider.interfaces.ILegalTagPublisher;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.Gson;
+
+@ConditionalOnProperty(
+	    value="ibm.legal.publisher.devnull", 
+	    havingValue = "false", 
+	    matchIfMissing = true)
+@Component
+public class LegalTagPublisherImpl implements ILegalTagPublisher {
+	
+	@Inject
+	IMessageFactory mq;
+
+	@Override
+	public void publish(String projectId, DpsHeaders headers, StatusChangedTags tags) throws Exception {
+
+		final int BATCH_SIZE = 50;
+		Gson gson = new Gson();
+		Map<String, String> message = new HashMap<>();
+		
+		for (int i = 0; i < tags.getStatusChangedTags().size(); i += BATCH_SIZE) {
+			
+			List<StatusChangedTag> batch = tags.getStatusChangedTags().subList(i,
+					Math.min(tags.getStatusChangedTags().size(), i + BATCH_SIZE));
+			String json = gson.toJson(batch);
+			message.put("data", json);
+			message.put(DpsHeaders.ACCOUNT_ID, headers.getPartitionIdWithFallbackToAccountId());
+			message.put(DpsHeaders.DATA_PARTITION_ID, headers.getPartitionIdWithFallbackToAccountId());
+			headers.addCorrelationIdIfMissing();
+			message.put(DpsHeaders.CORRELATION_ID, headers.getCorrelationId());
+			mq.sendMessage(IMessageFactory.LEGAL_QUEUE_NAME, gson.toJson(message));
+
+		}
+
+	}
+}
diff --git a/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/security/SecurityConfig.java b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/security/SecurityConfig.java
new file mode 100644
index 000000000..8fcf9df53
--- /dev/null
+++ b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/security/SecurityConfig.java
@@ -0,0 +1,24 @@
+package org.opengroup.osdu.legal.ibm.security;
+
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http
+                .csrf().disable()
+                .authorizeRequests()
+                .antMatchers("/v2/api-docs",
+                    "/configuration/ui",
+                    "/swagger-resources/**",
+                    "/configuration/security",
+                    "/swagger-ui.html",
+                    "/webjars/**").permitAll()
+                .anyRequest().authenticated().and().oauth2ResourceServer().jwt();
+    }
+}
diff --git a/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/security/WhoamiController.java b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/security/WhoamiController.java
new file mode 100644
index 000000000..a76c8ace7
--- /dev/null
+++ b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/security/WhoamiController.java
@@ -0,0 +1,25 @@
+package org.opengroup.osdu.legal.ibm.security;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+public class WhoamiController {
+    @RequestMapping(value = {"/", "/whoami"})
+    @ResponseBody
+    public String whoami() {
+        final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+
+        String userName = auth.getName();
+        String roles = String.valueOf(auth.getAuthorities());
+        String details = String.valueOf(auth.getPrincipal());
+
+        return "user: " + userName + "<BR>" +
+                "roles: " + roles + "<BR>" +
+                "details: " + details;
+    }
+}
+
diff --git a/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/tags/CloudantBackedLegalTag.java b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/tags/CloudantBackedLegalTag.java
new file mode 100644
index 000000000..cadec423d
--- /dev/null
+++ b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/tags/CloudantBackedLegalTag.java
@@ -0,0 +1,100 @@
+package org.opengroup.osdu.legal.ibm.tags;
+import java.lang.reflect.Type;
+import java.sql.Date;
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.opengroup.osdu.core.common.model.legal.LegalTag;
+import org.opengroup.osdu.core.common.model.legal.Properties;
+import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@Getter
+@Setter
+@ToString(callSuper=true)
+
+public class CloudantBackedLegalTag extends LegalTag {
+
+	@EqualsAndHashCode.Exclude
+	private String _rev = null;
+	 @Inject
+	 private static JaxRsDpsLog logger;
+
+	public static final JsonSerializer<LegalTag> serializer = new JsonSerializer<LegalTag>() {  
+		@Override
+		public JsonElement serialize(LegalTag src, Type typeOfSrc, JsonSerializationContext context) {
+			JsonObject json = new JsonObject();
+
+			if (src instanceof CloudantBackedLegalTag) {
+				json.addProperty("_rev", ((CloudantBackedLegalTag)src).get_rev());	
+			}
+
+			json.addProperty("_id", src.getId().toString());
+			json.addProperty("name", src.getName());
+			json.addProperty("description", src.getDescription());
+			json.addProperty("is_valid", src.getIsValid());
+
+			json.add("properties", context.serialize(src.getProperties(), Properties.class));
+
+			return json;
+		}
+	};
+
+	public static final JsonSerializer<Date> sqlDateSerializer = new JsonSerializer<Date>() {  
+		@Override
+		public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
+			return new JsonPrimitive(src.getTime());
+		}
+	};
+	
+	public static final JsonDeserializer<CloudantBackedLegalTag> deserializer = new JsonDeserializer<CloudantBackedLegalTag>() {
+
+		@Override
+		public CloudantBackedLegalTag deserialize(JsonElement json, Type arg1, JsonDeserializationContext context)
+				throws JsonParseException {
+
+			final CloudantBackedLegalTag result = new CloudantBackedLegalTag();
+			JsonObject src = json.getAsJsonObject();
+
+			try {
+				// Only the _rev object may be null
+				result.set_rev(Optional.ofNullable(src.get("_rev")).map(JsonElement::getAsString).orElse(null));
+
+				result.setId(src.get("_id").getAsLong());
+				result.setName(src.get("name").getAsString());
+				result.setDescription(src.get("description").getAsString());
+				result.setIsValid(src.get("is_valid").getAsBoolean());
+				result.setProperties(context.deserialize(src.get("properties"), Properties.class));
+
+			} catch (NullPointerException ex) {
+				logger.error("Serialized Legal Tag is missing one or more required fields");
+				throw new IllegalStateException("Serialized Legal Tag is missing one or more required fields");
+			}
+			return result;
+		}
+
+	};
+	
+	public static final JsonDeserializer<Date> sqlDateDeserializer = new JsonDeserializer<Date>() {
+
+		@Override
+		public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+				throws JsonParseException {
+			return new Date(json.getAsLong());
+		}
+		
+	};
+
+}
diff --git a/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/tags/CloudantLegalTagRepository.java b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/tags/CloudantLegalTagRepository.java
new file mode 100644
index 000000000..55368b99e
--- /dev/null
+++ b/provider/legal-ibm/src/main/java/org/opengroup/osdu/legal/ibm/tags/CloudantLegalTagRepository.java
@@ -0,0 +1,350 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.tags;
+
+import static com.cloudant.client.api.query.Expression.eq;
+import static com.cloudant.client.api.query.Expression.in;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+import org.opengroup.osdu.core.common.model.http.AppException;
+import org.opengroup.osdu.core.common.model.legal.LegalTag;
+import org.opengroup.osdu.core.common.model.legal.ListLegalTagArgs;
+import org.opengroup.osdu.core.ibm.auth.ServiceCredentials;
+import org.opengroup.osdu.core.ibm.cloudant.IBMCloudantClientFactory;
+import org.opengroup.osdu.legal.provider.interfaces.ILegalTagRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.stereotype.Repository;
+
+import com.cloudant.client.api.CloudantClient;
+import com.cloudant.client.api.Database;
+import com.cloudant.client.api.model.Response;
+import com.cloudant.client.api.query.EmptyExpression;
+import com.cloudant.client.api.query.ExecutionStats;
+import com.cloudant.client.api.query.QueryBuilder;
+import com.cloudant.client.api.query.QueryResult;
+import com.cloudant.client.api.query.Selector;
+import com.cloudant.client.org.lightcouch.DocumentConflictException;
+/**
+ * @author mbayser
+ *
+ */
+@Repository
+public class CloudantLegalTagRepository implements ILegalTagRepository {
+
+	@Value("${ibm.legal.db.url}") 
+	private String dbUrl;
+	@Value("${ibm.legal.db.apikey:#{null}}")
+	private String apiKey;
+	@Value("${ibm.legal.db.user:#{null}}")
+	private String dbUser;
+	@Value("${ibm.legal.db.password:#{null}}")
+	private String dbPassword;
+	@Value("${ibm.env.prefix:local-dev}")
+	private String dbNamePrefix;
+	
+	@Value("${ibm.legal.db.credentials:#{null}}")
+	private String credentialsJSON;
+	
+	@Value("${ibm.legal.db.name:legal-tags}")
+	private String dbName;
+
+	@Inject
+	private ResourceLoader resourceLoader;
+	
+	private static final Logger logger = LoggerFactory.getLogger(CloudantLegalTagRepository.class);
+
+	private IBMCloudantClientFactory cloudantFactory;
+	private CloudantClient cloudant;
+	private Database db = null;
+	private static final int CLOUDANT_CONFLICT_RETRIES = 10;
+	
+	// When using this class as a Bean, there must be a default constructor
+	// to create the object before the parameters can be injected. Only then
+	// can the initialization happen
+	public CloudantLegalTagRepository () {
+	}
+	
+	// This constructor is meant to facilitate testing
+	public CloudantLegalTagRepository(ServiceCredentials creds, String dbNamePrefix, String dataBaseName) throws MalformedURLException {
+		doInit(creds, dbNamePrefix, dataBaseName);
+	}
+	
+	@PostConstruct
+	public void init() throws IOException {
+		
+		ServiceCredentials creds = null;
+		if (dbUrl != null && apiKey != null) {
+			creds = new ServiceCredentials(dbUrl, apiKey);
+		} else if (dbUrl != null && dbUser != null) {
+			creds = new ServiceCredentials(dbUrl, dbUser, dbPassword);
+		} else {
+			creds = new ServiceCredentials(new InputStreamReader(resourceLoader.getResource(credentialsJSON).getInputStream()));
+		}
+		
+		doInit(creds, dbNamePrefix, dbName);
+        
+	}
+	
+	private void doInit(ServiceCredentials creds, String dbNamePrefix, String dataBaseName) throws MalformedURLException {
+		cloudantFactory = new IBMCloudantClientFactory(creds);
+		cloudantFactory.getGsonBuilder()
+			.registerTypeAdapter(java.sql.Date.class,          CloudantBackedLegalTag.sqlDateSerializer)
+			.registerTypeAdapter(java.sql.Date.class,          CloudantBackedLegalTag.sqlDateDeserializer)
+			.registerTypeAdapter(LegalTag.class,               CloudantBackedLegalTag.serializer)
+			.registerTypeAdapter(CloudantBackedLegalTag.class, CloudantBackedLegalTag.serializer)
+			.registerTypeAdapter(CloudantBackedLegalTag.class, CloudantBackedLegalTag.deserializer);
+		this.cloudant = cloudantFactory.getClient();
+		this.db = cloudantFactory.getDatabase(cloudant, dbNamePrefix, dataBaseName);		
+	}
+
+	@Override
+	public Long create(LegalTag legalTag) {
+		Long id = legalTag.getId();
+		
+		if (id == null) {
+			throw new NullPointerException("Legal tag with null ID cannot be save to cloudant");
+		}
+		try {
+			Response resp = db.save(legalTag);
+			db.ensureFullCommit();
+			
+			if (200 <= resp.getStatusCode() && resp.getStatusCode() < 300) {
+				
+				if (resp.getStatusCode() == 202) {
+					int retries = 3;
+					// This test is crude and I think it can fail in two ways:
+					// 1) Race condition with an update
+					// 2) Sufficient quorum was still not achieved.
+					// But it allows us to give a little more certainty
+					while(retries-- > 0) {
+						try {
+							CloudantBackedLegalTag verify = retrieveExactlyOne(id);
+							
+							if (!verify.get_rev().equals(resp.getRev())) {						
+								throw new AppException(409, "Cloudant sent a 202", "A LegalTag already exists for the given name");			
+							}
+							break;
+						} catch (IllegalArgumentException ex) {}
+					}
+	 			}
+				
+				return id;
+			} else {
+				logger.error("Failed to save legal tag in cloudant. Status code: {}, Reason: {}", resp.getStatusCode(), resp.getReason());
+				return null;
+			}
+		} catch(DocumentConflictException ex) {
+			throw new AppException(409, ex.getReason(), "A LegalTag already exists for the given name");
+		}
+	}
+
+	@Override
+	public Collection<LegalTag> get(long[] ids) {
+		// Would an "eq" be faster than an "in" if there was only one id?
+		
+		QueryBuilder builder = new QueryBuilder(in("_id", Arrays.stream(ids).mapToObj(l -> Long.toString(l)).toArray()));
+		
+		if (logger.isDebugEnabled()) {
+			builder.executionStats(true);
+		}
+		
+		QueryResult<CloudantBackedLegalTag> result = db.query(
+    			builder.build()
+    			, CloudantBackedLegalTag.class);
+		
+		logExecutionStat(db, result);
+		
+		if (result.getDocs().isEmpty()) {
+			return new ArrayList<LegalTag>();
+		} else {
+			if (result.getDocs().size() > ids.length) {
+				throw new IllegalStateException("Cardinality of Legal Tag result set is larger that the cardinality of IDs");
+			}
+			return Collections.unmodifiableList(result.getDocs());
+		}
+	}
+
+	@Override
+	public Boolean delete(LegalTag legalTag) {
+		
+		CloudantBackedLegalTag toDelete = null;
+
+		if (legalTag instanceof CloudantBackedLegalTag && ((CloudantBackedLegalTag)legalTag).get_rev() != null) {
+		//if (legalTag instanceof CloudantBackedLegalTag) {
+			toDelete = (CloudantBackedLegalTag)legalTag;
+		} else {
+			try {
+	    	toDelete = retrieveExactlyOne(legalTag.getId());
+	    	copyNewValuesToOldVersion(legalTag, toDelete);
+			} catch (IllegalArgumentException ex) {
+				return false;
+			}
+		}
+		
+		int countDown = CLOUDANT_CONFLICT_RETRIES;
+		
+		while (countDown-- > 0) {
+			try {
+				Response resp = db.remove(toDelete.getId().toString(), toDelete.get_rev());
+				if (200 <= resp.getStatusCode() && resp.getStatusCode() < 300) { 
+					return true;
+				} else {
+					logger.error("Failed to delete legal tag in cloudant. Status code: {}, Reason: {}", resp.getStatusCode(), resp.getReason());
+					return false;
+				}
+			} catch(DocumentConflictException ex) {
+				toDelete = retrieveExactlyOne(legalTag.getId());
+		    	copyNewValuesToOldVersion(legalTag, toDelete);
+			}
+		}
+		
+		throw new IllegalStateException("Cloudant delete had to be retried too many times due to update conflict");
+	}
+
+	private CloudantBackedLegalTag retrieveExactlyOne(Long id) {
+		
+		QueryBuilder builder = new QueryBuilder(eq("_id", id.toString()));
+		
+		if (logger.isDebugEnabled()) {
+			builder.executionStats(true);
+		}
+		
+		QueryResult<CloudantBackedLegalTag> result = db.query(builder.build()
+    			, CloudantBackedLegalTag.class);
+		
+		logExecutionStat(db, result);
+		
+		long numResults = result.getDocs().size();
+		
+    	if (numResults != 1) {
+    		if (numResults == 0) {
+    			throw new IllegalArgumentException("Legal tag does not exist in the database");
+    		} else {
+    			throw new IllegalStateException("Legal tag is not unique");
+    		}
+    	}
+    	return result.getDocs().get(0);
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.opengroup.osdu.legal.provider.interfaces.LegalTagRepository#update(org.opengroup.osdu.legal.tags.model.LegalTag)
+	 */
+	@Override
+	public LegalTag update(LegalTag newLegalTag) {
+
+		CloudantBackedLegalTag toUpdate = null;
+
+		if (newLegalTag instanceof CloudantBackedLegalTag) {
+			toUpdate = (CloudantBackedLegalTag)newLegalTag;
+		} else {
+			toUpdate = retrieveExactlyOne(newLegalTag.getId());
+			copyNewValuesToOldVersion(newLegalTag, toUpdate);
+		}
+
+		int countDown = CLOUDANT_CONFLICT_RETRIES;
+
+		while (countDown-- > 0) {
+			try {
+				Response resp = db.update(toUpdate);
+				if (200 <= resp.getStatusCode() && resp.getStatusCode() < 300) { 
+					return newLegalTag;
+				} else {
+					logger.error("Failed to save legal tag in cloudant. Status code: {}, Reason: {}", resp.getStatusCode(), resp.getReason());
+					return null;
+				}
+			} catch(DocumentConflictException ex) {
+				toUpdate = retrieveExactlyOne(newLegalTag.getId());
+				copyNewValuesToOldVersion(newLegalTag, toUpdate);
+			}
+		}
+
+		throw new IllegalStateException("Cloudant update had to be retried too many times due to update conflict");
+
+	}
+	
+	private void copyNewValuesToOldVersion(LegalTag old, LegalTag _new) {
+		assert(old.getId().equals(_new.getId()));
+		old.setName(_new.getName());
+		old.setDescription(_new.getDescription());
+		old.setIsValid(_new.getIsValid());
+		old.setProperties(_new.getProperties());
+	}
+
+	/* (non-Javadoc)
+	 * @see org.opengroup.osdu.legal.provider.interfaces.LegalTagRepository#list(org.opengroup.osdu.legal.tags.dataaccess.ListLegalTagArgs)
+	 */
+	@Override
+	public Collection<LegalTag> list(ListLegalTagArgs args) {		
+
+		Selector query = EmptyExpression.empty(); 
+		// Since the isValid attribute is a Boolean rather than a bool
+		// we will interpret this a three state flag
+		// The byoc doesn't help to clarify that because it hits a
+		// NullPointerException when comparing a bool this a null Boolean,
+		// But at least the behavior is consistent when the limit is not
+		// null
+		// Both the commented code and the code below pass all acceptance tests
+		// query = eq("is_valid", Optional.ofNullable(args.getIsValid()).orElse(true).booleanValue());
+		if (args.getIsValid() != null) {
+			query = eq("is_valid", args.getIsValid().booleanValue());
+		}
+
+
+		QueryBuilder builder = new QueryBuilder(query);
+
+		builder.bookmark(args.getCursor());
+
+		if (logger.isDebugEnabled()) {
+			builder.executionStats(true);
+		}
+
+		if (args.getLimit() > 0) {
+			builder.limit(args.getLimit());
+		}
+
+		QueryResult<CloudantBackedLegalTag> result = db.query(builder.build()
+				, CloudantBackedLegalTag.class);
+
+		logExecutionStat(db, result);
+
+		if (args.getLimit() > 0) {
+			args.setCursor(result.getBookmark());
+		}
+			return Collections.unmodifiableCollection(result.getDocs());
+	}
+	
+	private void logExecutionStat(Database db, QueryResult<?> result) {
+		final ExecutionStats stats = result.getExecutionStats();
+		if (stats != null) {
+			logger.debug("Query on {} returned {} results in {} ms. Total docs examined: {}. TotalKeys examined: {}. Warning message: {}", db.info().getDbName(), stats.getResultsReturned(), stats.getExecutionTimeMs(), stats.getTotalDocsExamined(), stats.getTotalKeysExamined(), result.getWarning());
+		}
+	}
+
+
+}
diff --git a/provider/legal-ibm/src/main/resources/application.yml b/provider/legal-ibm/src/main/resources/application.yml
new file mode 100644
index 000000000..9e7072815
--- /dev/null
+++ b/provider/legal-ibm/src/main/resources/application.yml
@@ -0,0 +1,35 @@
+server:
+  servlet:
+    contextPath: /api/legal/v1/
+  port: 8080 
+
+spring:
+  security:
+    oauth2:
+      resourceserver:
+        jwt:
+          jwk-set-uri: https://some.keycloak.com/auth/realms/OSDU/protocol/openid-connect/certs
+
+REGION: us-central
+AUTHORIZE_API: http://entitlements:8080/api/entitlements/v1
+LEGAL_HOSTNAME: notused
+CRON_JOB_IP: 10.0.0.1
+LOG_PREFIX: legal
+
+ibm:
+  env:
+    prefix: acceptance-test
+  legal:
+    publisher:
+      devnull: true
+    db:
+      name: legal-tags
+      credentials: file:/somepath
+      user: change
+      password: change
+  tenant:
+    db:
+      name: tenant-info
+      url: ${ibm.legal.db.url}
+      user: ${ibm.legal.db.user}
+      password: ${ibm.legal.db.password}
diff --git a/provider/legal-ibm/src/main/resources/logback.groovy b/provider/legal-ibm/src/main/resources/logback.groovy
new file mode 100644
index 000000000..287380cc6
--- /dev/null
+++ b/provider/legal-ibm/src/main/resources/logback.groovy
@@ -0,0 +1,43 @@
+import org.slf4j.bridge.SLF4JBridgeHandler
+import ch.qos.logback.classic.jul.LevelChangePropagator
+
+// see also: http://logback.qos.ch/manual/configuration.html#LevelChangePropagator
+// performance speedup for redirected JUL loggers
+def lcp = new LevelChangePropagator()
+lcp.context = context
+lcp.resetJUL = true
+context.addListener(lcp)
+
+// needed only for the JUL bridge: http://stackoverflow.com/a/9117188/1915920
+java.util.logging.LogManager.getLogManager().reset()
+SLF4JBridgeHandler.removeHandlersForRootLogger()
+SLF4JBridgeHandler.install()
+java.util.logging.Logger.getLogger( "" ).setLevel( java.util.logging.Level.FINEST )
+
+def logPattern = "|%.-1level| [%thread] %20.30logger{30}| %msg%n"
+appender("STDOUT", ConsoleAppender) {
+    encoder(PatternLayoutEncoder) {
+        pattern = logPattern
+    }
+}
+root(TRACE, ["STDOUT"])
+
+def rootLvl = INFO
+logger( "antlr", rootLvl )
+logger( "de", rootLvl )
+logger( "ch", rootLvl )
+logger( "com", rootLvl )
+logger( "java", rootLvl )
+logger( "javassist", rootLvl )
+logger( "javax", rootLvl )
+logger( "junit", rootLvl )
+logger( "groovy", rootLvl )
+logger( "net", rootLvl )
+logger( "org", rootLvl )
+logger( "sun", rootLvl )
+logger( "org.opengroup.osdu", DEBUG )
+logger( "org.springframework.web.servlet", INFO )
+logger( "org.springframework.security", INFO )
+logger( "org.apache.http", INFO )
+
+scan("30 seconds")  // reload/apply-on-change config every x sec
\ No newline at end of file
diff --git a/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/EntitlementsFactoryByoc.java b/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/EntitlementsFactoryByoc.java
new file mode 100644
index 000000000..79936ac56
--- /dev/null
+++ b/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/EntitlementsFactoryByoc.java
@@ -0,0 +1,31 @@
+//  Copyright © Microsoft Corporation
+//
+//  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
+//
+//       http://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.legal.ibm.api;
+
+import org.opengroup.osdu.core.common.model.http.DpsHeaders;
+import org.opengroup.osdu.core.common.entitlements.IEntitlementsFactory;
+import org.opengroup.osdu.core.common.entitlements.IEntitlementsService;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Component;
+
+@Component
+@Primary
+public class EntitlementsFactoryByoc implements IEntitlementsFactory {
+    @Override
+    public IEntitlementsService create(DpsHeaders headers) {
+        return new EntitlementsServiceByoc(headers);
+    }
+}
+
diff --git a/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/EntitlementsServiceByoc.java b/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/EntitlementsServiceByoc.java
new file mode 100644
index 000000000..10566a30f
--- /dev/null
+++ b/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/EntitlementsServiceByoc.java
@@ -0,0 +1,93 @@
+//  Copyright © Microsoft Corporation
+//
+//  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
+//
+//       http://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.legal.ibm.api;
+
+import org.apache.http.HttpStatus;
+import org.opengroup.osdu.core.common.model.entitlements.*;
+import org.opengroup.osdu.core.common.model.http.DpsHeaders;
+import org.opengroup.osdu.core.common.entitlements.IEntitlementsService;
+import org.opengroup.osdu.core.common.model.legal.ServiceConfig;
+import org.opengroup.osdu.core.common.http.HttpResponse;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class EntitlementsServiceByoc implements IEntitlementsService {
+    DpsHeaders headers;
+
+    public EntitlementsServiceByoc(DpsHeaders headers) {
+        this.headers = headers;
+    }
+
+    @Override
+    public MemberInfo addMember(GroupEmail groupEmail, MemberInfo memberInfo) throws EntitlementsException {
+        return null;
+    }
+
+    @Override
+    public Members getMembers(GroupEmail groupEmail, GetMembers getMembers) throws EntitlementsException {
+        return null;
+    }
+
+    @Override
+    public Groups getGroups() throws EntitlementsException {
+        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+        String email = auth.getName();
+        List<GroupInfo> giList = new ArrayList();
+        Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
+        for (GrantedAuthority authority : authorities) {
+            GroupInfo gi = new GroupInfo();
+            String role = authority.getAuthority();
+            if (role.startsWith(ServiceConfig.PREFIX)) {
+                role = role.substring(ServiceConfig.PREFIX.length());
+            }
+            gi.setName(role);
+            gi.setEmail(email);
+            giList.add(gi);
+        }
+        if (giList.size() > 0) {
+            Groups groups = new Groups();
+            groups.setGroups(giList);
+            groups.setDesId(email);
+            return groups;
+        }
+
+        HttpResponse response = new HttpResponse();
+        response.setResponseCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
+        throw new EntitlementsException("no authorities found", response);
+    }
+
+    @Override
+    public GroupInfo createGroup(CreateGroup createGroup) throws EntitlementsException {
+        return null;
+    }
+
+    @Override
+    public void deleteMember(String s, String s1) throws EntitlementsException {
+    }
+
+    @Override
+    public Groups authorizeAny(String... strings) throws EntitlementsException {
+        return null;
+    }
+
+    @Override
+    public void authenticate() throws EntitlementsException {
+    }
+}
diff --git a/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/LegalTagApiTest.java b/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/LegalTagApiTest.java
new file mode 100644
index 000000000..c65d6b8a2
--- /dev/null
+++ b/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/api/LegalTagApiTest.java
@@ -0,0 +1,85 @@
+package org.opengroup.osdu.legal.ibm.api;
+
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import javax.inject.Inject;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.opengroup.osdu.core.common.model.http.AppException;
+import org.opengroup.osdu.core.common.model.http.RequestInfo;
+import org.opengroup.osdu.core.common.model.legal.ServiceConfig;
+import org.opengroup.osdu.core.common.model.tenant.TenantInfo;
+import org.opengroup.osdu.legal.api.LegalTagApi;
+import org.opengroup.osdu.legal.ibm.LegalApplication;
+import org.opengroup.osdu.legal.tags.LegalTagService;
+import org.opengroup.osdu.legal.tags.dto.LegalTagDto;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@Ignore
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes={LegalApplication.class})
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LegalTagApiTest {
+	
+    @Mock
+    TenantInfo tenantInfo;
+
+    @Mock
+    LegalTagService legalTagService;
+
+    @Mock
+    RequestInfo requestInfo;
+
+    @InjectMocks
+    @Inject
+    private LegalTagApi sut;
+
+    @Before
+    public void Setup() {
+       initMocks(this);
+       when(requestInfo.getTenantInfo()).thenReturn(tenantInfo);
+    }
+
+    @Test(expected = AuthenticationCredentialsNotFoundException.class)
+    public void givenUnauthenticated_whenCallCreateLegalTag_thenThrowsException(){
+        LegalTagDto legalTag = new LegalTagDto();
+        this.sut.createLegalTag(legalTag);
+    }
+
+    @WithMockUser(username="admin", roles={ServiceConfig.LEGAL_ADMIN})
+    @Test
+    public void given1AuthenticatedAdmin_whenCallCreateLegalTag_thenOk() {
+        LegalTagDto legalTag = new LegalTagDto();
+        Assert.assertEquals(HttpStatus.CREATED, this.sut.createLegalTag(legalTag).getStatusCode());
+    }
+
+    @WithMockUser(username="viewer", roles={ServiceConfig.LEGAL_USER})
+    @Test
+    public void givenAuthenticatedViewer_whenCallCreateLegalTag_thenForbidden() {
+        try {
+            LegalTagDto legalTag = new LegalTagDto();
+            this.sut.createLegalTag(legalTag);
+        } catch (AppException e) {
+            Assert.assertEquals(HttpStatus.UNAUTHORIZED.value(), e.getError().getCode());
+        }
+    }
+
+    @WithMockUser(username="viewer", roles={ServiceConfig.LEGAL_USER})
+    @Test
+    public void given2AuthenticatedViewer_whenCallGetLegalTag_thenOK() {
+        Assert.assertEquals(HttpStatus.OK, this.sut.listLegalTags(true).getStatusCode());
+    }
+}
diff --git a/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/tags/LegalTagTests.java b/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/tags/LegalTagTests.java
new file mode 100644
index 000000000..1b3c29669
--- /dev/null
+++ b/provider/legal-ibm/src/test/java/org/opengroup/osdu/legal/ibm/tags/LegalTagTests.java
@@ -0,0 +1,519 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.tags;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.sql.Date;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.opengroup.osdu.core.common.model.legal.LegalTag;
+import org.opengroup.osdu.core.common.model.legal.ListLegalTagArgs;
+import org.opengroup.osdu.core.common.model.legal.Properties;
+import org.opengroup.osdu.core.ibm.auth.ServiceCredentials;
+import org.opengroup.osdu.core.ibm.cloudant.IBMCloudantClientFactory;
+
+import com.cloudant.client.api.CloudantClient;
+import com.cloudant.client.api.Database;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+
+/**
+ * @author mbayser
+ *
+ */
+@Ignore
+@RunWith(MockitoJUnitRunner.class)
+public class LegalTagTests {
+
+	private CloudantClient cloudant;
+	private static final String databaseNameFormat = "legal-tag-test-%s";
+	private String databaseName;
+	private DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
+	private Database db;
+
+	private CloudantLegalTagRepository repo;
+
+
+	public LegalTagTests() {
+	}
+
+    @Before
+    public void setup() throws Exception {
+
+    	databaseName = String.format(databaseNameFormat, dtf.format(LocalDateTime.now()));
+
+    	IBMCloudantClientFactory fact;
+    	ServiceCredentials creds;
+    	try {
+    		 fact = new IBMCloudantClientFactory();
+    		 creds = ServiceCredentials.environmentSupplied();
+    	} catch (Exception e) {
+    		 fact = new IBMCloudantClientFactory(System.getenv("CLOUDANT_URL"), System.getenv("CLOUDANT_KEY"));
+    		 creds = new ServiceCredentials(System.getenv("CLOUDANT_URL"), System.getenv("CLOUDANT_KEY"));
+    	}
+
+    	fact.getGsonBuilder()
+			.registerTypeAdapter(java.sql.Date.class,          CloudantBackedLegalTag.sqlDateSerializer)
+			.registerTypeAdapter(java.sql.Date.class,          CloudantBackedLegalTag.sqlDateDeserializer)
+			.registerTypeAdapter(LegalTag.class,               CloudantBackedLegalTag.serializer)
+			.registerTypeAdapter(CloudantBackedLegalTag.class, CloudantBackedLegalTag.serializer)
+			.registerTypeAdapter(CloudantBackedLegalTag.class, CloudantBackedLegalTag.deserializer);
+    	cloudant = fact.getClient();
+
+        db = fact.getDatabase("test",databaseName);
+        db.getDBUri();
+
+    	repo = new CloudantLegalTagRepository(creds, "test", databaseName);
+
+    }
+
+    @After
+    public void teardown() throws Exception {
+    	cloudant.deleteDB(IBMCloudantClientFactory.dbNameRule("test", databaseName));
+    }
+
+    @Test
+    public void testEquals() {
+
+    	CloudantBackedLegalTag original = (CloudantBackedLegalTag)configureTag(new CloudantBackedLegalTag());
+
+    	CloudantBackedLegalTag copy1 = (CloudantBackedLegalTag)configureTag(new CloudantBackedLegalTag());
+    	assertEquals(original, copy1);
+
+    	copy1.set_rev("qflkhfeiurlew");
+
+    	assertEquals(original, copy1);
+
+    	LegalTag copy2 = configureTag(new LegalTag());
+
+    	assertTrue(original.equals(copy2));
+    	assertTrue(copy2.equals(original));
+    }
+
+    @SuppressWarnings("deprecation")
+	@Test
+    public void testSerialization() {
+
+    	CloudantBackedLegalTag original = new CloudantBackedLegalTag();
+    	original.setId(1001L);
+    	original.set_rev("3-rev");
+    	original.setName("TENANT1");
+    	original.setDescription("description");
+    	original.setIsValid(true);
+
+    	Properties props = new Properties();
+    	props.setCountryOfOrigin(Arrays.asList("Erebor"));
+    	props.setDataType("gem");
+    	props.setSecurityClassification("bestows the right to rule");
+    	props.setPersonalData("Belongs to the Folk of Durin");
+    	props.setExportClassification("Kings Jewel");
+    	props.setOriginator("Thorin Oakenshield");
+    	props.setContractId("Burglary contract");
+    	props.setExpirationDate(new Date(2941, 11, 32));
+
+    	original.setProperties(props);
+
+    	Gson gson = new GsonBuilder()
+    	    	.registerTypeAdapter(CloudantBackedLegalTag.class, CloudantBackedLegalTag.serializer)
+    	    	.registerTypeAdapter(LegalTag.class, CloudantBackedLegalTag.serializer)
+    	    	.registerTypeAdapter(CloudantBackedLegalTag.class, CloudantBackedLegalTag.deserializer)
+    	    	.create();
+
+    	JsonElement serialized = gson.toJsonTree(original);
+    	System.out.println(serialized);
+
+    	CloudantBackedLegalTag deserialized = gson.fromJson(serialized, CloudantBackedLegalTag.class);
+
+    	assertEquals(original.getId(),                           deserialized.getId());
+    	assertEquals(original.getName(),                         deserialized.getName());
+    	assertEquals(original.getDescription(),                  deserialized.getDescription());
+    	assertEquals(original.getIsValid(),                      deserialized.getIsValid());
+    	assertEquals(original.getProperties(),                   deserialized.getProperties());
+
+    	// Just to make sure that lombok is doing its job
+    	assertEquals(original.hashCode(),                        deserialized.hashCode());
+    	assertEquals(original, deserialized);
+    }
+
+    @Test
+    public void testCreate() throws Exception {
+
+    	LegalTag tag1 = createValidLegalTag("tag1");
+    	Long id1 = tag1.getId();
+
+    	Long saved_id1 = repo.create(tag1);
+
+    	assertEquals(id1, saved_id1);
+
+
+    	try {
+        	LegalTag nullIdtag = createValidLegalTag("nulltag");
+        	nullIdtag.setId(null);
+    		repo.create(nullIdtag);
+    		fail("Expected exception");
+    	} catch (NullPointerException e) {	}
+
+
+    	LegalTag tag2 = createValidLegalTag("tag2");
+    	tag2.setIsValid(true);
+    	Long id2 = tag2.getId();
+
+    	Long saved_id2 = repo.create(tag2);
+
+    	assertEquals(id2, saved_id2);
+
+    }
+
+    @Test
+    public void testGet() throws Exception {
+    	LegalTag tag1 = createValidLegalTag("tag1");
+    	Long id1 = tag1.getId();
+
+    	Long saved_id1 = repo.create(tag1);
+    	assertEquals(id1, saved_id1);
+
+    	LegalTag tag2 = createValidLegalTag("tag2");
+    	tag2.setIsValid(true);
+    	Long id2 = tag2.getId();
+
+    	Long saved_id2 = repo.create(tag2);
+
+    	assertEquals(id2, saved_id2);
+
+    	Collection<LegalTag> retrv1 = repo.get(new long[] { id1 });
+    	assertEquals(retrv1.size(), 1);
+
+    	LegalTag retrv1_tag1 = retrv1.iterator().next();
+    	assertEquals(tag1, retrv1_tag1);
+    }
+
+    @Test
+    public void testNone() throws Exception {
+
+    	LegalTag tag1 = createValidLegalTag("tag1");
+    	Long id1 = tag1.getId();
+
+    	Long saved_id1 = repo.create(tag1);
+    	assertEquals(id1, saved_id1);
+
+    	LegalTag tag2 = createValidLegalTag("tag2");
+    	tag2.setIsValid(true);
+    	Long id2 = tag2.getId();
+
+    	Long saved_id2 = repo.create(tag2);
+
+    	assertEquals(id2, saved_id2);
+
+    	Collection<LegalTag> retrv1 = repo.get(new long[] { 4324L });
+    	assertEquals(retrv1.size(), 0);
+    }
+
+
+    @Test
+    public void testGetMultiple() throws Exception {
+
+    	LegalTag tag1 = createValidLegalTag("tag1");
+    	Long id1 = tag1.getId();
+
+    	Long saved_id1 = repo.create(tag1);
+    	assertEquals(id1, saved_id1);
+
+    	LegalTag tag2 = createValidLegalTag("tag2");
+    	tag2.setIsValid(true);
+    	Long id2 = tag2.getId();
+
+    	Long saved_id2 = repo.create(tag2);
+
+    	assertEquals(id2, saved_id2);
+
+    	Collection<LegalTag> retrv1 = repo.get(new long[] { id1, id2 });
+    	assertEquals(retrv1.size(), 2);
+
+    	Iterator<LegalTag> it = retrv1.iterator();
+
+    	LegalTag retrv1_tag1 = it.next();
+    	assertEquals(tag1, retrv1_tag1);
+    	LegalTag retrv1_tag2 = it.next();
+    	assertEquals(tag2, retrv1_tag2);
+    }
+
+    @Test
+    public void testUpdate() throws Exception {
+
+    	LegalTag tag1 = createValidLegalTag("tag1");
+    	Long id1 = tag1.getId();
+    	tag1.setDescription("description1");
+
+    	try {
+    		repo.update(tag1);
+    		fail("expected exception when updating object not present in the database");
+    	} catch (IllegalArgumentException ex) {}
+
+    	Long saved_id1 = repo.create(tag1);
+    	assertEquals(id1, saved_id1);
+
+    	Collection<LegalTag> retrv1 = repo.get(new long[] { id1 });
+    	assertEquals(retrv1.size(), 1);
+
+    	Iterator<LegalTag> retrv1_it = retrv1.iterator();
+
+    	LegalTag retrv1_tag1 = retrv1_it.next();
+    	assertEquals(tag1, retrv1_tag1);
+
+    	retrv1_tag1.setDescription("description2");
+
+    	// This update will work without extra logic because retrv1_tag1 is
+    	// actually a CloudantBackedLegalTag with a valid _rev
+    	repo.update(retrv1_tag1);
+
+    	Collection<LegalTag> retrv2 = repo.get(new long[] { id1 });
+    	assertEquals(retrv2.size(), 1);
+
+    	Iterator<LegalTag> retrv2_it = retrv2.iterator();
+
+    	LegalTag retrv2_tag1 = retrv2_it.next();
+
+    	assertNotEquals(tag1, retrv2_tag1);
+    	assertEquals(retrv1_tag1, retrv2_tag1);
+
+
+    	LegalTag tag1_equivalent = createValidLegalTag("tag1");
+    	tag1.setDescription("description3");
+    	assertEquals(id1, tag1_equivalent.getId());
+
+    	// This update would fail it the update method didn't handle the missing _rev
+    	repo.update(tag1_equivalent);
+
+
+    	// This update would fail it the update method didn't handle the outdated _rev
+    	repo.update(retrv2_tag1);
+
+    }
+
+    @Test
+    public void testDelete () throws Exception {
+
+    	LegalTag tag1 = createValidLegalTag("tag1");
+    	Long id1 = tag1.getId();
+    	tag1.setDescription("description1");
+
+  		assertFalse(repo.delete(tag1));
+
+    	CloudantBackedLegalTag cloudantBacked = (CloudantBackedLegalTag)configureTag(new CloudantBackedLegalTag());
+    	assertFalse(repo.delete(cloudantBacked));
+
+    	Long saved_id1 = repo.create(tag1);
+    	assertEquals(id1, saved_id1);
+
+    	Collection<LegalTag> retrv1 = repo.get(new long[] { id1 });
+    	assertEquals(retrv1.size(), 1);
+
+    	assertTrue(repo.delete(tag1));
+
+    	Collection<LegalTag> retrv2 = repo.get(new long[] { id1 });
+    	assertEquals(retrv2.size(), 0);
+
+    	repo.create(tag1);
+
+    	Collection<LegalTag> retrv3 = repo.get(new long[] { id1 });
+    	assertEquals(retrv3.size(), 1);
+
+    	Iterator<LegalTag> retrv3_it = retrv3.iterator();
+
+    	LegalTag retrv3_tag1 = retrv3_it.next();
+    	assertEquals(tag1, retrv3_tag1);
+
+    	retrv3_tag1.setDescription("description2");
+    	assertNotEquals(tag1, retrv3_tag1);
+
+    	repo.update(retrv3_tag1);
+
+    	Collection<LegalTag> retrv4 = repo.get(new long[] { id1 });
+    	assertEquals(retrv4.size(), 1);
+
+
+    	// This would fail without the retry on conflict logic
+    	assertTrue(repo.delete(retrv3_tag1));
+
+    	Collection<LegalTag> retrv5 = repo.get(new long[] { id1 });
+    	assertEquals(retrv5.size(), 0);
+
+    }
+
+    @Test
+    public void testList() throws Exception {
+
+    	LegalTag tag1 = createValidLegalTag("tag1");
+    	tag1.setIsValid(false);
+    	repo.create(tag1);
+    	LegalTag tag2 = createValidLegalTag("tag2");
+    	tag2.setIsValid(true);
+    	repo.create(tag2);
+    	LegalTag tag3 = createValidLegalTag("tag3");
+    	tag3.setIsValid(false);
+    	repo.create(tag3);
+    	LegalTag tag4 = createValidLegalTag("tag4");
+    	tag4.setIsValid(true);
+    	repo.create(tag4);
+    	LegalTag tag5 = createValidLegalTag("tag5");
+    	tag5.setIsValid(false);
+    	repo.create(tag5);
+    	LegalTag tag6 = createValidLegalTag("tag6");
+    	tag6.setIsValid(true);
+    	repo.create(tag6);
+
+    	ListLegalTagArgs allTagsNoPaging = new ListLegalTagArgs();
+    	allTagsNoPaging.setIsValid(null);
+    	allTagsNoPaging.setLimit(0);
+
+    	Set<String> allNames = repo.list(allTagsNoPaging).stream().map(t -> t.getName()).collect(Collectors.toSet());
+
+    	assertEquals(6,  allNames.size());
+    	assertTrue(allNames.contains("tag1"));
+    	assertTrue(allNames.contains("tag2"));
+    	assertTrue(allNames.contains("tag3"));
+    	assertTrue(allNames.contains("tag4"));
+    	assertTrue(allNames.contains("tag5"));
+    	assertTrue(allNames.contains("tag6"));
+
+
+    	ListLegalTagArgs validTagsNoPaging = new ListLegalTagArgs();
+    	validTagsNoPaging.setIsValid(true);
+    	validTagsNoPaging.setLimit(0);
+
+    	Set<String> validNames = repo.list(validTagsNoPaging).stream().map(t -> t.getName()).collect(Collectors.toSet());
+
+    	assertEquals(3, validNames.size());
+    	assertTrue(validNames.contains("tag2"));
+    	assertTrue(validNames.contains("tag4"));
+    	assertTrue(validNames.contains("tag6"));
+
+    	HashSet<String> allNamesSet = new HashSet<>(Arrays.asList(
+    			"tag1",
+    			"tag2",
+    			"tag3",
+    			"tag4",
+    			"tag5",
+    			"tag6"
+		));
+
+    	ListLegalTagArgs allTagsWithPaging = new ListLegalTagArgs();
+    	allTagsWithPaging.setIsValid(null);
+    	allTagsWithPaging.setLimit(1);
+
+    	assertEquals(6, allNamesSet.size());
+
+    	List<String> page1 = repo.list(allTagsWithPaging).stream().map(t -> t.getName()).collect(Collectors.toList());
+    	assertEquals(1, page1.size());
+    	allNamesSet.remove(page1.get(0));
+    	assertEquals(5, allNamesSet.size());
+
+    	List<String> page2 = repo.list(allTagsWithPaging).stream().map(t -> t.getName()).collect(Collectors.toList());
+    	assertEquals(1, page2.size());
+    	allNamesSet.remove(page2.get(0));
+    	assertEquals(4, allNamesSet.size());
+
+    	List<String> page3 = repo.list(allTagsWithPaging).stream().map(t -> t.getName()).collect(Collectors.toList());
+    	assertEquals(1, page3.size());
+    	allNamesSet.remove(page3.get(0));
+    	assertEquals(3, allNamesSet.size());
+
+    	List<String> page4 = repo.list(allTagsWithPaging).stream().map(t -> t.getName()).collect(Collectors.toList());
+    	assertEquals(1, page4.size());
+    	allNamesSet.remove(page4.get(0));
+    	assertEquals(2, allNamesSet.size());
+
+    	List<String> page5 = repo.list(allTagsWithPaging).stream().map(t -> t.getName()).collect(Collectors.toList());
+    	assertEquals(1, page5.size());
+    	allNamesSet.remove(page5.get(0));
+    	assertEquals(1, allNamesSet.size());
+
+    	List<String> page6 = repo.list(allTagsWithPaging).stream().map(t -> t.getName()).collect(Collectors.toList());
+    	assertEquals(1, page6.size());
+    	allNamesSet.remove(page6.get(0));
+    	assertEquals(0, allNamesSet.size());
+
+    	List<String> page7 = repo.list(allTagsWithPaging).stream().map(t -> t.getName()).collect(Collectors.toList());
+    	assertEquals(0, page7.size());
+
+    	List<String> page8 = repo.list(allTagsWithPaging).stream().map(t -> t.getName()).collect(Collectors.toList());
+    	assertEquals(0, page8.size());
+    }
+
+    @SuppressWarnings("deprecation")
+	public static LegalTag configureTag(LegalTag original) {
+
+    	original.setId(1001L);
+    	original.setName("TENANT1");
+    	original.setDescription("description");
+    	original.setIsValid(true);
+
+    	Properties props = new Properties();
+    	props.setCountryOfOrigin(Arrays.asList("Erebor"));
+    	props.setDataType("gem");
+    	props.setSecurityClassification("bestows the right to rule");
+    	props.setPersonalData("Belongs to the Folk of Durin");
+    	props.setExportClassification("King's Jewel");
+    	props.setOriginator("Thorin Oakenshield");
+    	props.setContractId("Burglary contract");
+    	props.setExpirationDate(new Date(2941, 11, 32));
+    	original.setProperties(props);
+    	return original;
+    }
+
+    public static LegalTag createValidLegalTag(String name){
+    	LegalTag legalTag = new LegalTag();
+        legalTag.setProperties(createValidProperties());
+        legalTag.setName(name);
+        legalTag.setIsValid(false);
+        legalTag.setDefaultId();
+        return legalTag;
+    }
+    @SuppressWarnings("serial")
+	public static Properties createValidProperties(){
+        Properties properties = new Properties();
+        properties.setCountryOfOrigin(new ArrayList<String>(){{add("USA");}});
+        properties.setExpirationDate(new Date(System.currentTimeMillis()));
+        properties.setOriginator("MyCompany");
+        properties.setContractId("Unknown");
+        properties.setDataType("Tranferred Data");
+        properties.setPersonalData("Sensitive Personal Information");
+        properties.setSecurityClassification("Confidential");
+        properties.setExportClassification("ECCN");
+        return properties;
+    }
+}
diff --git a/provider/legal-ibm/src/test/resources/logback.groovy b/provider/legal-ibm/src/test/resources/logback.groovy
new file mode 100644
index 000000000..7a86fe105
--- /dev/null
+++ b/provider/legal-ibm/src/test/resources/logback.groovy
@@ -0,0 +1,40 @@
+import org.slf4j.bridge.SLF4JBridgeHandler
+import ch.qos.logback.classic.jul.LevelChangePropagator
+
+// see also: http://logback.qos.ch/manual/configuration.html#LevelChangePropagator
+// performance speedup for redirected JUL loggers
+def lcp = new LevelChangePropagator()
+lcp.context = context
+lcp.resetJUL = true
+context.addListener(lcp)
+
+// needed only for the JUL bridge: http://stackoverflow.com/a/9117188/1915920
+java.util.logging.LogManager.getLogManager().reset()
+SLF4JBridgeHandler.removeHandlersForRootLogger()
+SLF4JBridgeHandler.install()
+java.util.logging.Logger.getLogger( "" ).setLevel( java.util.logging.Level.FINEST )
+
+def logPattern = "|%.-1level| [%thread] %20.30logger{30}| %msg%n"
+appender("STDOUT", ConsoleAppender) {
+    encoder(PatternLayoutEncoder) {
+        pattern = logPattern
+    }
+}
+root(TRACE, ["STDOUT"])
+
+def rootLvl = INFO
+logger( "antlr", rootLvl )
+logger( "de", rootLvl )
+logger( "ch", rootLvl )
+logger( "com", rootLvl )
+logger( "java", rootLvl )
+logger( "javassist", rootLvl )
+logger( "javax", rootLvl )
+logger( "junit", rootLvl )
+logger( "groovy", rootLvl )
+logger( "net", rootLvl )
+logger( "org", rootLvl )
+logger( "sun", rootLvl )
+logger( "org.opengroup.osdu", DEBUG )
+
+scan("30 seconds")  // reload/apply-on-change config every x sec
\ No newline at end of file
diff --git a/testing/legal-test-aws/pom.xml b/testing/legal-test-aws/pom.xml
index c95a2c10d..f8ab425c4 100644
--- a/testing/legal-test-aws/pom.xml
+++ b/testing/legal-test-aws/pom.xml
@@ -29,7 +29,6 @@
         <maven.compiler.source>1.8</maven.compiler.source>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.main.basedir>${project.basedir}</project.main.basedir>
-        <MY_TENANT>opendes</MY_TENANT>
     </properties>
     <dependencies>
         <!-- Internal packages -->
@@ -40,8 +39,8 @@
         </dependency>
         <dependency>
             <groupId>org.opengroup.osdu.core.aws</groupId>
-            <artifactId>aws-osdu-util</artifactId>
-            <version>0.0.9</version>
+            <artifactId>os-core-lib-aws</artifactId>
+            <version>0.0.10</version>
         </dependency>
 
         <!-- AWS managed packages -->
@@ -158,12 +157,7 @@
                 <configuration>
                     <trimStackTrace>false</trimStackTrace>
                     <systemPropertyVariables>
-                        <HOST_URL>http://localhost:8082/api/legal/v1/</HOST_URL>
-                        <ENTITLEMENT_URL>broken</ENTITLEMENT_URL>
-                        <AWS_S3_ENDPOINT>s3.us-east-1.amazonaws.com</AWS_S3_ENDPOINT>
-                        <AWS_S3_REGION>us-east-1</AWS_S3_REGION>
                         <buildDirectory>${project.build.directory}</buildDirectory>
-                        <MY_TENANT>opendes</MY_TENANT>
                     </systemPropertyVariables>
                 </configuration>
             </plugin>
diff --git a/testing/legal-test-aws/src/test/java/org/opengroup/osdu/legal/util/AwsLegalTagUtils.java b/testing/legal-test-aws/src/test/java/org/opengroup/osdu/legal/util/AwsLegalTagUtils.java
index fe80fe77a..223331e0b 100644
--- a/testing/legal-test-aws/src/test/java/org/opengroup/osdu/legal/util/AwsLegalTagUtils.java
+++ b/testing/legal-test-aws/src/test/java/org/opengroup/osdu/legal/util/AwsLegalTagUtils.java
@@ -21,7 +21,7 @@ import org.opengroup.osdu.core.aws.s3.S3Config;
 
 public class AwsLegalTagUtils extends LegalTagUtils {
     private static final String FILE_NAME = "Legal_COO.json";
-    private static final String BUCKET_NAME_AWS = "dev-osdu-legal-config";
+    private static final String BUCKET_NAME_AWS = System.getProperty("S3_LEGAL_CONFIG_BUCKET", System.getenv("S3_LEGAL_CONFIG_BUCKET"));
 
     private final static String COGNITO_CLIENT_ID_PROPERTY = "AWS_COGNITO_CLIENT_ID";
     private final static String COGNITO_AUTH_FLOW_PROPERTY = "AWS_COGNITO_AUTH_FLOW";
diff --git a/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/LegalTagUtils.java b/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/LegalTagUtils.java
index 70a548dc9..dd1d238a4 100644
--- a/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/LegalTagUtils.java
+++ b/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/LegalTagUtils.java
@@ -22,7 +22,7 @@ public abstract class LegalTagUtils extends TestUtils {
 	public abstract String accessToken() throws Exception; 
 
     private static InputStream getTestFileInputStream(String fileName) throws IOException {
-        return LegalTagUtils.class.getClass().getResourceAsStream("/" + fileName);
+        return LegalTagUtils.class.getResourceAsStream("/" + fileName);
     }
 
     protected static String readTestFile(String fileName) throws IOException {
diff --git a/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/TestUtils.java b/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/TestUtils.java
index 5abd44182..1f026e853 100644
--- a/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/TestUtils.java
+++ b/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/TestUtils.java
@@ -29,7 +29,6 @@ public class TestUtils {
 
 	public TestUtils(boolean enforceHttp){
 		baseUrl = System.getProperty("HOST_URL", System.getenv("HOST_URL"));
-		//baseUrl = System.getenv("HOST_URL");
 		if(baseUrl == null || baseUrl.contains("-null")) {
 			baseUrl = "https://localhost:8443/api/legal/v1/";
 		}
@@ -48,7 +47,6 @@ public class TestUtils {
 
 	public static String getMyProjectAccountId(){
 		return System.getProperty("MY_TENANT_PROJECT", System.getenv("MY_TENANT_PROJECT"));
-		//return System.getenv("MY_TENANT_PROJECT");
 	}
 
 	public String getBaseHost() {return baseUrl.substring(8,baseUrl.length()-1);}
@@ -60,7 +58,6 @@ public class TestUtils {
 	
 	public static String getMyDataPartition(){
 		return System.getProperty("MY_TENANT", System.getenv("MY_TENANT"));
-		//return System.getenv("MY_TENANT");
 	}
 
 	public ClientResponse send(String path, String httpMethod, String token, String requestBody, String query)
diff --git a/testing/legal-test-ibm/pom.xml b/testing/legal-test-ibm/pom.xml
new file mode 100644
index 000000000..a904ddc8c
--- /dev/null
+++ b/testing/legal-test-ibm/pom.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2017-2019 Schlumberger
+
+  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
+
+       http://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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.opengroup.osdu.legal</groupId>
+    <artifactId>legal-test-ibm</artifactId>
+    <version>0.0.1</version>
+    <packaging>jar</packaging>
+
+    <properties>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.main.basedir>${project.basedir}</project.main.basedir>
+        <!--
+        <PROJECT_ID>${PROJECT_ID}</PROJECT_ID>
+        <HOST_URL>${HOST_URL}</HOST_URL>
+        <ENTITLEMENT_URL>${ENTITLEMENT_URL}</ENTITLEMENT_URL>
+        <INTEGRATION_TESTER>${INTEGRATION_TESTER}</INTEGRATION_TESTER>
+        <INTEGRATION_TEST_AUDIENCE>${INTEGRATION_TEST_AUDIENCE}</INTEGRATION_TEST_AUDIENCE>
+        <MY_TENANT>${MY_TENANT}</MY_TENANT>
+        <CLIENT_TENANT>${CLIENT_TENANT}</CLIENT_TENANT>
+        <MY_TENANT_PROJECT>${MY_TENANT_PROJECT}</MY_TENANT_PROJECT>
+        <CLIENT_TENANT_PROJECT>${CLIENT_TENANT_PROJECT}</CLIENT_TENANT_PROJECT>
+        -->
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.opengroup.osdu.legal</groupId>
+            <artifactId>legal-test-core</artifactId>
+            <version>0.0.2-SNAPSHOT</version>
+        </dependency>
+
+        <!-- Tests -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>        
+    </dependencies>
+
+    <repositories>
+        <repository>
+            <id>dev-azure-com-slb-des-ext-collaboration-os-core</id>
+            <url>https://pkgs.dev.azure.com/slb-des-ext-collaboration/_packaging/os-core/maven/v1</url>
+            <releases>
+              <enabled>true</enabled>
+            </releases>
+            <snapshots>
+              <enabled>true</enabled>
+              <updatePolicy>never</updatePolicy>
+            </snapshots>
+          </repository>
+    </repositories>
+    <build>
+        <defaultGoal>test</defaultGoal>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.4.2</version>
+                <configuration>
+                    <trimStackTrace>false</trimStackTrace>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/testing/legal-test-ibm/run_service.sh b/testing/legal-test-ibm/run_service.sh
new file mode 100644
index 000000000..704f52eec
--- /dev/null
+++ b/testing/legal-test-ibm/run_service.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+export TENANT_INFO_DATABASE=acceptance-test-tenant-info
+export LEGAL_TAG_DATABASE=acceptance-test-legal-tags
+export COUNTRIES_DATABASE=acceptance-test-countries
+
+# snagged from: https://stackoverflow.com/a/51264222/26510
+function toAbsPath {
+    local target
+    target="$1"
+
+    if [ "$target" == "." ]; then
+        echo "$(pwd)"
+    elif [ "$target" == ".." ]; then
+        echo "$(dirname "$(pwd)")"
+    else
+        echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
+    fi
+}
+
+function getScriptDir(){
+  local SOURCED
+  local RESULT
+  (return 0 2>/dev/null) && SOURCED=1 || SOURCED=0
+
+  if [ "$SOURCED" == "1" ]
+  then
+    RESULT=$(dirname "$1")
+  else
+    RESULT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+  fi
+  toAbsPath "$RESULT"
+}
+
+SCRIPT_DIR=$(getScriptDir "$0")
+
+CRED_FILE=$(basename ${IBM_CREDENTIALS_FILE})
+
+mkdir config
+cp ${IBM_CREDENTIALS_FILE} config/
+cat <<EOF > config/application.yml
+ibm:
+  tenant:
+    cloudant:
+      dbName: tenant-info
+      credentials: file:config/${CRED_FILE}
+  legal:
+    cloudant:
+      dbName: legal-tags
+      credentials: file:config/${CRED_FILE}
+EOF
+
+java -jar ${SCRIPT_DIR}/../../provider/legal-ibm/target/legal-ibm-0.0.4-SNAPSHOT-spring-boot.jar
diff --git a/testing/legal-test-ibm/run_tests.sh b/testing/legal-test-ibm/run_tests.sh
new file mode 100644
index 000000000..af90a9e52
--- /dev/null
+++ b/testing/legal-test-ibm/run_tests.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+if [ -z "$LEGAL_TEST_TOKEN" ]
+then
+    echo "env var LEGAL_TEST_TOKEN not set"
+    exit 1
+fi
+
+DATABASE_PREFIX=acceptance-test
+export COUNTRIES_DATABASE=$DATABASE_PREFIX-countries
+export TENANT_INFO_DATABASE=$DATABASE_PREFIX-tenant-info
+export LEGAL_TAG_DATABASE=$DATABASE_PREFIX-legal-tags
+export DATA_PARTITION_ID=data-partition-id
+export HOST_URL=http://localhost:8080/api/legal/v1/
+#export HOST_URL=http://localhost:8080/api/legal/v1/
+export HOST_URL=https://os-legal-ibm-osdu-r2.osduadev-a1c3eaf78a86806e299f5f3f207556f0-0000.us-south.containers.appdomain.cloud/api/legal/v1/
+export MY_TENANT=TENANT1
+export MY_TENANT_PROJECT=PROJECT1
+
+mvn test
diff --git a/testing/legal-test-ibm/setup_acceptance.sh b/testing/legal-test-ibm/setup_acceptance.sh
new file mode 100644
index 000000000..18e3162e3
--- /dev/null
+++ b/testing/legal-test-ibm/setup_acceptance.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+BASE_URL=$(jq -r ".url" < ${IBM_CREDENTIALS_FILE})
+USERNAME=$(jq -r ".username" < ${IBM_CREDENTIALS_FILE})
+PASSWORD=$(jq -r ".password" < ${IBM_CREDENTIALS_FILE})
+
+DATABASE_PREFIX=acceptance-test
+
+curl -XPUT -u $USERNAME:$PASSWORD $BASE_URL/$DATABASE_PREFIX-countries
+curl -XPUT -u $USERNAME:$PASSWORD $BASE_URL/$DATABASE_PREFIX-legal-tags
+curl -XPUT -u $USERNAME:$PASSWORD $BASE_URL/$DATABASE_PREFIX-tenant-info
+
+# snagged from: https://stackoverflow.com/a/51264222/26510
+function toAbsPath {
+    local target
+    target="$1"
+
+    if [ "$target" == "." ]; then
+        echo "$(pwd)"
+    elif [ "$target" == ".." ]; then
+        echo "$(dirname "$(pwd)")"
+    else
+        echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
+    fi
+}
+
+function getScriptDir(){
+  local SOURCED
+  local RESULT
+  (return 0 2>/dev/null) && SOURCED=1 || SOURCED=0
+
+  if [ "$SOURCED" == "1" ]
+  then
+    RESULT=$(dirname "$1")
+  else
+    RESULT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+  fi
+  toAbsPath "$RESULT"
+}
+
+SCRIPT_DIR=$(getScriptDir "$0")
+
+
+
+TEST_COUNTRIES=${SCRIPT_DIR}/../legal-test-core/src/main/resources/TenantConfigTestingPurpose.json
+cat $TEST_COUNTRIES | jq -rc '{ "docs": [.[] + {"tenant": "TENANT1", "region": "us"}]}' | curl -XPOST --data-binary @- -H"Content-type: application/json" -u $USERNAME:$PASSWORD $BASE_URL/$DATABASE_PREFIX-countries/_bulk_docs
+
+TEST_TENANT=${SCRIPT_DIR}/src/test/resources/Tenant.json
+curl -XPOST --data-binary @${TEST_TENANT} -H"Content-type: application/json" -u $USERNAME:$PASSWORD $BASE_URL/$DATABASE_PREFIX-tenant-info/_bulk_docs
+
+TEST_TAGS=${SCRIPT_DIR}/src/test/resources/InitialTags.json
+curl -XPOST --data-binary @${TEST_TAGS} -H"Content-type: application/json" -u $USERNAME:$PASSWORD $BASE_URL/$DATABASE_PREFIX-legal-tags/_bulk_docs
diff --git a/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestCreateLegalTagApiAcceptance.java b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestCreateLegalTagApiAcceptance.java
new file mode 100644
index 000000000..4825dc665
--- /dev/null
+++ b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestCreateLegalTagApiAcceptance.java
@@ -0,0 +1,124 @@
+package org.opengroup.osdu.legal.ibm.acceptanceTests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opengroup.osdu.legal.acceptanceTests.CreateLegalTagApiAcceptanceTests;
+import org.opengroup.osdu.legal.ibm.acceptanceTests.util.IBMLegalTagUtils;
+import org.opengroup.osdu.legal.util.TestUtils;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+public class TestCreateLegalTagApiAcceptance extends CreateLegalTagApiAcceptanceTests {
+
+    @Before
+    @Override
+    public void setup() throws Exception {
+        this.legalTagUtils = new IBMLegalTagUtils();
+        super.setup();
+    }
+
+    @After
+    @Override
+    public void teardown() throws Exception {
+        super.teardown();
+        this.legalTagUtils = null;
+    }
+    
+    /*
+     * This test has to be modified slightly for cloudant. Since the exact same document is
+     * uploaded several times, in the case where cloudant returns 202, it doesn't make
+     * sense to ask if 1 out of N writes is the winner. 
+     */
+    @Override 
+    @Test
+    public void should_onlyLetAMaximumOf1LegaltagBeCreated_when_tryingToCreateMultipleVersionsOfTheSameContractAtTheSameTime() throws Exception {
+        ExecutorService executor = Executors.newFixedThreadPool(10);
+        List<Callable<ClientResponse>> tasks = new ArrayList<>();
+
+        for (int i = 0; i < 10; i++) {
+            Callable<ClientResponse> task = () -> {
+                try {
+                    return legalTagUtils.create(name);
+                } catch (Exception ex) {
+                    return null;
+                }
+            };
+            tasks.add(task);
+        }
+
+        List<Future<ClientResponse>> responses = executor.invokeAll(tasks);
+        executor.shutdown();
+        executor.awaitTermination(20, TimeUnit.SECONDS);
+
+        int sucessResponseCount = 0;
+        int non409ErrorResponseCount = 0;
+        for (Future<ClientResponse> future : responses) {
+            if (future.get().getStatus() == 201) {
+                sucessResponseCount++;
+            } else if (future.get().getStatus() != 409) {
+                non409ErrorResponseCount++;
+            }
+        }
+        assertTrue("Expected at least one 1 successful response. Actual " + sucessResponseCount, sucessResponseCount >= 1);
+        assertEquals(0, non409ErrorResponseCount);
+    }
+
+    
+
+    /*
+     * Now this test is designed so that we can test if we can say who the winner is when documents
+     * actually conflict 
+     */
+    @Test
+    public void should_onlyLetAMaximumOf1LegaltagBeCreated_when_tryingToCreateMultipleDifferentVersionsOfTheSameContractAtTheSameTime() throws Exception {
+        ExecutorService executor = Executors.newFixedThreadPool(10);
+        List<Callable<ClientResponse>> tasks = new ArrayList<>();
+
+        for (int i = 0; i < 10; i++) {
+            Callable<ClientResponse> task = () -> {
+                try {
+                    return legalTagUtils.create(
+                    		"US", 
+                    		name, 
+                    		"2099-12-25", 
+                    		"Transferred Data", 
+                    		TestUtils.getMyDataPartition(),
+                    		"<my description>"+Thread.currentThread().getId());
+                    		
+                } catch (Exception ex) {
+                    return null;
+                }
+            };
+            tasks.add(task);
+        }
+
+        List<Future<ClientResponse>> responses = executor.invokeAll(tasks);
+        executor.shutdown();
+        executor.awaitTermination(20, TimeUnit.SECONDS);
+
+        int sucessResponseCount = 0;
+        int non409ErrorResponseCount = 0;
+        for (Future<ClientResponse> future : responses) {
+            if (future.get().getStatus() == 201) {
+                sucessResponseCount++;
+            } else if (future.get().getStatus() != 409) {
+                non409ErrorResponseCount++;
+            }
+        }
+        assertTrue("Expected at most one 1 successful response. Actual " + sucessResponseCount, sucessResponseCount <= 1);
+        assertEquals(0, non409ErrorResponseCount);
+    }
+
+}
diff --git a/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestDeleteLegalTagApiAcceptance.java b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestDeleteLegalTagApiAcceptance.java
new file mode 100644
index 000000000..5259f2b4e
--- /dev/null
+++ b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestDeleteLegalTagApiAcceptance.java
@@ -0,0 +1,46 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.acceptanceTests;
+
+
+import org.junit.After;
+import org.junit.Before;
+import org.opengroup.osdu.legal.acceptanceTests.DeleteLegalTagApiAcceptanceTests;
+import org.opengroup.osdu.legal.ibm.acceptanceTests.util.IBMLegalTagUtils;
+
+/**
+ * @author mbayser
+ *
+ */
+
+public class TestDeleteLegalTagApiAcceptance extends DeleteLegalTagApiAcceptanceTests {
+
+    @Before
+    @Override
+    public void setup() throws Exception {
+        this.legalTagUtils = new IBMLegalTagUtils();
+        super.setup();
+    }
+
+    @After
+    @Override
+    public void teardown() throws Exception {
+        super.teardown();
+        this.legalTagUtils = null;
+    }
+
+}
+
diff --git a/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagApiAcceptance.java b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagApiAcceptance.java
new file mode 100644
index 000000000..eb8b168de
--- /dev/null
+++ b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagApiAcceptance.java
@@ -0,0 +1,40 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.acceptanceTests;
+
+import org.junit.After;
+import org.junit.Before;
+import org.opengroup.osdu.legal.acceptanceTests.GetLegalTagApiAcceptanceTests;
+import org.opengroup.osdu.legal.ibm.acceptanceTests.util.IBMLegalTagUtils;
+
+public class TestGetLegalTagApiAcceptance extends GetLegalTagApiAcceptanceTests {
+
+    @Before
+    @Override
+    public void setup() throws Exception {
+        this.legalTagUtils = new IBMLegalTagUtils();
+        super.setup();
+    }
+
+    @After
+    @Override
+    public void teardown() throws Exception {
+        super.teardown();
+        this.legalTagUtils = null;
+    }
+
+}
+
diff --git a/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagPropertiesApiAcceptance.java b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagPropertiesApiAcceptance.java
new file mode 100644
index 000000000..612a9be18
--- /dev/null
+++ b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagPropertiesApiAcceptance.java
@@ -0,0 +1,40 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.acceptanceTests;
+
+import org.junit.After;
+import org.junit.Before;
+import org.opengroup.osdu.legal.acceptanceTests.GetLegalTagPropertiesApiAcceptanceTests;
+import org.opengroup.osdu.legal.ibm.acceptanceTests.util.IBMLegalTagUtils;
+
+
+public class TestGetLegalTagPropertiesApiAcceptance extends GetLegalTagPropertiesApiAcceptanceTests {
+
+    @Before
+    @Override
+    public void setup() throws Exception {
+        this.legalTagUtils = new IBMLegalTagUtils();
+        super.setup();
+    }
+
+    @After
+    @Override
+    public void teardown() throws Exception {
+        super.teardown();
+        this.legalTagUtils = null;
+    }
+
+}
\ No newline at end of file
diff --git a/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagsApiAcceptance.java b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagsApiAcceptance.java
new file mode 100644
index 000000000..2c490d638
--- /dev/null
+++ b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestGetLegalTagsApiAcceptance.java
@@ -0,0 +1,43 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.acceptanceTests;
+
+import org.junit.After;
+import org.junit.Before;
+import org.opengroup.osdu.legal.acceptanceTests.GetLegalTagsApiAcceptanceTests;
+import org.opengroup.osdu.legal.ibm.acceptanceTests.util.IBMLegalTagUtils;
+
+/**
+ * @author mbayser
+ *
+ */
+public class TestGetLegalTagsApiAcceptance extends GetLegalTagsApiAcceptanceTests {
+
+    @Before
+    @Override
+    public void setup() throws Exception {
+        this.legalTagUtils = new IBMLegalTagUtils();
+        super.setup();
+    }
+
+    @After
+    @Override
+    public void teardown() throws Exception  {
+        super.teardown();
+        this.legalTagUtils = null;
+    }
+
+}
\ No newline at end of file
diff --git a/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestListLegalTagsApiAcceptance.java b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestListLegalTagsApiAcceptance.java
new file mode 100644
index 000000000..4e2f5724b
--- /dev/null
+++ b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestListLegalTagsApiAcceptance.java
@@ -0,0 +1,43 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.acceptanceTests;
+
+import org.junit.After;
+import org.junit.Before;
+import org.opengroup.osdu.legal.acceptanceTests.ListLegalTagsApiAcceptanceTests;
+import org.opengroup.osdu.legal.ibm.acceptanceTests.util.IBMLegalTagUtils;
+
+/**
+ * @author mbayser
+ *
+ */
+public class TestListLegalTagsApiAcceptance extends ListLegalTagsApiAcceptanceTests {
+
+    @Before
+    @Override
+    public void setup() throws Exception {
+        this.legalTagUtils = new IBMLegalTagUtils();
+        super.setup();
+    }
+
+    @After
+    @Override
+    public void teardown() throws Exception {
+        super.teardown();
+        this.legalTagUtils = null;
+    }
+
+}
\ No newline at end of file
diff --git a/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestUpdateLegalTagApiAcceptance.java b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestUpdateLegalTagApiAcceptance.java
new file mode 100644
index 000000000..a053ea810
--- /dev/null
+++ b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestUpdateLegalTagApiAcceptance.java
@@ -0,0 +1,43 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.acceptanceTests;
+
+import org.junit.After;
+import org.junit.Before;
+import org.opengroup.osdu.legal.acceptanceTests.UpdateLegalTagApiAcceptanceTests;
+import org.opengroup.osdu.legal.ibm.acceptanceTests.util.IBMLegalTagUtils;
+
+/**
+ * @author mbayser
+ *
+ */
+public class TestUpdateLegalTagApiAcceptance extends UpdateLegalTagApiAcceptanceTests {
+
+    @Before
+    @Override
+    public void setup() throws Exception {
+        this.legalTagUtils = new IBMLegalTagUtils();
+        super.setup();
+    }
+
+    @After
+    @Override
+    public void teardown() throws Exception {
+        super.teardown();
+        this.legalTagUtils = null;
+    }
+
+}
diff --git a/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestValidateLegalTagsApiAcceptance.java b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestValidateLegalTagsApiAcceptance.java
new file mode 100644
index 000000000..ca3e96673
--- /dev/null
+++ b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/TestValidateLegalTagsApiAcceptance.java
@@ -0,0 +1,42 @@
+// (C) Copyright IBM Corporation 2019
+// U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
+//
+//  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
+//
+//       http://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.legal.ibm.acceptanceTests;
+
+import org.junit.After;
+import org.junit.Before;
+import org.opengroup.osdu.legal.acceptanceTests.ValidateLegalTagsApiAcceptanceTests;
+import org.opengroup.osdu.legal.ibm.acceptanceTests.util.IBMLegalTagUtils;
+
+/**
+ * @author mbayser
+ *
+ */
+public class TestValidateLegalTagsApiAcceptance extends ValidateLegalTagsApiAcceptanceTests {
+
+    @Before
+    @Override
+    public void setup() throws Exception {
+        this.legalTagUtils = new IBMLegalTagUtils();
+        super.setup();
+    }
+
+    @After
+    @Override
+    public void teardown() throws Exception {
+        super.teardown();
+        this.legalTagUtils = null;
+    }
+}
diff --git a/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/util/IBMLegalTagUtils.java b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/util/IBMLegalTagUtils.java
new file mode 100644
index 000000000..661ca0c09
--- /dev/null
+++ b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/ibm/acceptanceTests/util/IBMLegalTagUtils.java
@@ -0,0 +1,21 @@
+package org.opengroup.osdu.legal.ibm.acceptanceTests.util;
+
+import org.opengroup.osdu.legal.util.LegalTagUtils;
+
+public class IBMLegalTagUtils extends LegalTagUtils {
+
+	public IBMLegalTagUtils() {
+		// TODO Auto-generated constructor stub
+	}
+
+	@Override
+	public void uploadTenantTestingConfigFile() {
+		// TODO Auto-generated method stub
+
+	}
+
+	@Override
+	public String accessToken() throws Exception {
+		return System.getenv("LEGAL_TEST_TOKEN");
+	}
+}
diff --git a/testing/legal-test-ibm/src/test/resources/InitialTags.json b/testing/legal-test-ibm/src/test/resources/InitialTags.json
new file mode 100644
index 000000000..34ed7cfff
--- /dev/null
+++ b/testing/legal-test-ibm/src/test/resources/InitialTags.json
@@ -0,0 +1,76 @@
+{
+  "docs": [
+    {
+      "_id": "1001",
+      "name": "valid_tag1",
+      "description": "description",
+      "is_valid": true,
+      "properties": {
+        "countryOfOrigin": [
+          "US"
+        ],
+        "contractId": "Contract 1",
+        "expirationDate": 1765464244000,
+        "originator": "John Smith",
+        "dataType": "Wellbore",
+        "securityClassification": "confidential",
+        "personalData": "none",
+        "exportClassification": "restricted"
+      }
+    },
+    {
+      "_id": "1002",
+      "name": "valid_tag1",
+      "description": "description",
+      "is_valid": true,
+      "properties": {
+        "countryOfOrigin": [
+          "US"
+        ],
+        "contractId": "Contract 1",
+        "expirationDate": 1765464244000,
+        "originator": "John Smith",
+        "dataType": "Wellbore",
+        "securityClassification": "confidential",
+        "personalData": "none",
+        "exportClassification": "restricted"
+      }
+    },
+    {
+      "_id": "1003",
+      "name": "invalid_tag1",
+      "description": "description",
+      "is_valid": false,
+      "properties": {
+        "countryOfOrigin": [
+          "US"
+        ],
+        "contractId": "Contract 1",
+        "expirationDate": 1765464244000,
+        "originator": "John Smith",
+        "dataType": "Wellbore",
+        "securityClassification": "confidential",
+        "personalData": "none",
+        "exportClassification": "restricted"
+      }
+    },
+    {
+      "_id": "1291004374",
+      "name": "TENANT1-dps-integration-test-1566474656479",
+      "description": "invalid date",
+      "is_valid": true,
+      "properties": {
+        "countryOfOrigin": [
+          "US"
+        ],
+        "contractId": "A1234",
+        "expirationDate": 1134312244000,
+        "originator": "MyCompany",
+        "dataType": "Transferred Data",
+        "securityClassification": "Public",
+        "personalData": "No Personal Data",
+        "exportClassification": "EAR99"
+      }
+    }
+  ]
+}
diff --git a/testing/legal-test-ibm/src/test/resources/Tenant.json b/testing/legal-test-ibm/src/test/resources/Tenant.json
new file mode 100644
index 000000000..965fdf592
--- /dev/null
+++ b/testing/legal-test-ibm/src/test/resources/Tenant.json
@@ -0,0 +1,17 @@
+{
+  "docs": [
+    {
+      "_id": "1001",
+      "name": "TENANT1",
+      "projectId": "PROJECT1",
+      "serviceAccount": "SERVICE1",
+      "complianceRuleSet": "shared",
+      "dataPartitionId": "PARTITION1",
+      "crmAccountIds": [
+        "CRM0001",
+        "CRM0002",
+        "TENANT1"
+      ]
+    }
+  ]
+}
diff --git a/testing/legal-test-ibm/teardown_acceptance.sh b/testing/legal-test-ibm/teardown_acceptance.sh
new file mode 100644
index 000000000..6009e2c56
--- /dev/null
+++ b/testing/legal-test-ibm/teardown_acceptance.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+DATABASE_PREFIX=acceptance-test
+BASE_URL=$(jq -r ".url" < ${IBM_CREDENTIALS_FILE})
+USERNAME=$(jq -r ".username" < ${IBM_CREDENTIALS_FILE})
+PASSWORD=$(jq -r ".password" < ${IBM_CREDENTIALS_FILE})
+
+curl -XDELETE -u $USERNAME:$PASSWORD $BASE_URL/$DATABASE_PREFIX-countries
+curl -XDELETE -u $USERNAME:$PASSWORD $BASE_URL/$DATABASE_PREFIX-legal-tags
+curl -XDELETE -u $USERNAME:$PASSWORD $BASE_URL/$DATABASE_PREFIX-tenant-info
-- 
GitLab