Commit f29a3516 authored by David Diederich's avatar David Diederich
Browse files

Initial Import

parents
##
# Needed to run the service
##
export service_domain_name=
export aad_client_id=
export azure.activedirectory.AppIdUri=
export azure.activedirectory.session-stateless=
export cosmosdb_database=
export KEYVAULT_URI=
export appinsights_key=
export spring.application.name=
export AZURE_CLIENT_ID=
export AZURE_TENANT_ID=
export AZURE_CLIENT_SECRET=
##
# Needed to run the integration tests
##
export ENTITLEMENT_URL=
export DOMAIN=
export MY_TENANT=
export INTEGRATION_TESTER=
export AZURE_TESTER_SERVICEPRINCIPAL_SECRET=
export AZURE_AD_TENANT_ID=
export AZURE_AD_APP_RESOURCE_ID=
export AZURE_AD_OTHER_APP_RESOURCE_ID=
export EXPIRED_TOKEN=
export ENTITLEMENT_MEMBER_NAME_INVALID=
export ENTITLEMENT_MEMBER_NAME_VALID=
export ENTITLEMENT_GROUP_NAME_VALID=
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
.mvn/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
### VS Code ###
.vscode/
# How Security works with OSDU/OpenDES on Azure
> OSDU (Open Subsurface Data Universe) and OpenDES (Open Data Eco System) are in the process of merging. When we built the MVP(Minimum Viable Product) on Azure, it was still OpenDES. Below we use the terminology OpenDES instead of OSDU/OpenDES.
## Application Registration in Azure Active Directory (AAD)
* You can register OpenDES services in your main corporate AAD tenant,ex. ```microsoft.com```; or another tenant that you own, ex. ```opendes.onmicrosoft.com```.
* If you expect users from different AAD tenants, ex. alice@chevron.com and bob@equinor.com to use the same service you deploy, then register it as a multi-tenant application, otherwise single tenant.
* OpenDES services only use AAD for authentication and expect the user's OpenID Connect (OIDC) ID_TOKEN passed in the ```Authorization``` header as each request goes through multiple services. For this reason, you only need to register one application in Azure AD, with the minimum privilege of ```sign in and read user profile```.
![sign in and read user profile](images/01_signin_profile.png)
* OAuth2 implicit flow is considered less secure than, for example, access code or client credential flow. When OpenDES goes GA, we expect frontend applications able to prompt the user for log in to implement the adequate flow. In MVP, we have a simple javascript implementing implicit flow in the Storage Service, so developers have an easy way to log in to Azure and get the OIDC token. If you want to enable this, then
1. add Storage Service root URL to ```Redict URIs``` as shown below.
![Redirect URIs](images/02_redirect_uri.png)
2. tick ```Implicit Grant ID token``` in app registration as shown below.
![Implicit Grant ID_TOKEN](images/03_implicit_flow.png)
* In order for AAD service principals to call the OpenDES services using OAuth client credential flow, the application must also expose an API as shown below.
![Expose an API](images/04_expose_api.png)
## Authentication
* OpenDES is built as a set of backend REST APIs. Most of these services, ex. Storage Service and Legal Service, have a user context. They take an OIDC token in the ```Authorization``` head. The services don't have a way to prompt the user for login, and don't care how the token is obtained, as long as it's a valid token.
* Some services don't have a user context. For example, Indexer Service listens on message queues and transfers messages to downstream services. It's not called by a user. These services, as well as integration tests, use a service principal to authenticate against AAD to obtain a JWT token to access other services that require a user context.
* While we could use Azure MSI (Managed Service Identity) for Indexer Service to access other services, it might become necessary to have different identities for different OpenDES tenants to access resources. The concurrent configuration of OpenDES tenants in CosmosDB allows for this requirement, as shown below.
![Tenant info](images/11_tenantinfo.png)
* Integration tests use multiple service principals representing different roles.
* The service principals are registered without requiring any API permissions as authentication is implemented in the application.
* To authenticate with a service principal, construct a request similar to the following:
![Service Principal login](images/05_sp_login.png)
* The services are implemented with [AAD Springboot Starter](https://github.com/microsoft/azure-spring-boot/tree/master/azure-spring-boot-starters/azure-active-directory-spring-boot-starter). It provides multiple ways to authenticate with AAD. Even though we are not using AAD app roles for authorization, we are using [AADAppRoleStatelessAuthenticationFilter](https://github.com/microsoft/azure-spring-boot/tree/master/azure-spring-boot-starters/azure-active-directory-spring-boot-starter) because our APIs, unlike web applications, are stateless.
* To test any service APIs, navigate to the Storage Service endpoint and log in to AAD. You may be prompted to consent to allow the app to sign you in and read profile. Then copy the returned token in the UI, navigate to the ```/swagger``` UI, and paste the token for swagger to put in the ```Authorization``` header, as shown below:
![Azure AD login](images/06_swagger_auth.png)
* Service to service authentication - most services verify the JWT token in the ```Authorization``` header. Entitlements Service, given its special role, also requires an ```AppKey``` in the header. Only callers that pass in the correct ```AppKey``` can access Entitlements Service APIs. In production, these backend services will likely be deployed in a private VNET rather than public internet. Additional protections, such as Web Application Firewalls, can also be added.
## Authorization
OpenDES services check the groups a user belongs to to determine whether to grant access to resources, including services and data. There are several ways to implement groups in AAD, each with its pros and cons -
1. Use AAD security groups.
* Pros
* Fairly simple to implement, with AAD already providing management APIs and UI.
* Cons
* Application must have permission to read/write AAD directories, which is usually prohibitive in most enterprises.
* External users must be added with AAD B2B or must have a shadow identity in the AAD tenant.
* A user can belong to hundreds of groups causing the ODIC token to be too large to pass in the ```Authorization``` header. Therefore it may be necessary to additionally query the AAD graph API to get the user groups, and cache the groups.
* Groups exist in the directory, and if not explicitly deleted, will remain even when the application is deleted.
2. Use AAD app roles.
* Pros
* The groups (app roles) are within the scope of the application. If the application is removed, the groups will be removed as well.
* External users can be assigned to the same groups without having to be added to the same tenant as the application.
* The number of groups an application requires is typically much smaller than the groups in the directory a user could belong to, and can fit in an OIDC token without having to make additional calls.
* Cons
* When the number of groups grows more than a few, it becomes quite an overhead to maintain the application manifest and the mapping of users to their groups, at least in the AAD UI.
* Application still needs directory admin permission to assign users to groups, or with AAD Premium, create an AAD security group for each app group and assign the users to the security groups. This results in the similar issues as directly using AAD groups.
3. Manage user roles in the application.
* Pros
* Provides all the flexibility to meet application requirements.
* Doesn't require any AAD permission other than signing in the user.
* Cons
* Requires external databases to persist user group mapping.
We choose the third option considering that requiring directory read/write permission may be too restrictive for most enterprises to adopt the solution, as well as the fact that OpenDES has more than just a few groups, even nested groups.
## OpenDES authorization concepts
OpenDES introduces the following concepts, and they all work together to determine whether a user can access a resource -
* __Domain__ - identifies an environment that OpenDES services operate on, doesn't have to match users email domain or AAD domain, although in production, it's probably close to an AAD tenant domain. Domain is configured in the Entitlements Service's ```application.properties```.
* __Tenant__ - identifies a partition of data. Each domain can have multiple tenants. In the current OpenDES implementation on GCP, data partitions are also isolated by physical boundaries. Think, for example, each partition has its own cloud storage account and database. In MVP on Azure, tenant only represents logical isoloation. Tenant is passed in the ```data-partition-id``` header of each request and configured in CosmosDB.
* __Group__ - identifies user's permissions. It's configured in CosmosDB.
* There are two categories of permissions. Permissions to access API, ex. ```service.storage.admin```; and permissions to access data, ex. ```data.datalake.viewer```.
* It doesn't look like there's implicit symentic meanings of the group names, ex. ```service.storage.admin``` doesn't have access to API annotated only with ```service.storage.viewer```.
* There's a concept of nested groups, for example, ```user.admin``` could include both ```service.storage.admin``` and ```data.datalake.admin```. This is not implemented in MVP on Azure.
Here's an example of how users are configured in CosmosDB:
![User info configuration](images/07_userinfo.png)
## OpenDES authorization logic in an example
Here's a sample of the authorization rules applied when a data record is created or updated in OpenDES:
* user must belong to one of the groups specified in the target service controller.
![service authorization](images/12_serviceauth.png)
* ```data-partition-id``` in the header represents the user ```tenant```. It must match the ```tenant``` specified in the body.
* user must belong to one of the groups specified in the ```acl``` of the body.
* ```acl``` must have the format ```group@tenant.domain```.
![header](images/09_header.png)
![record](images/08_record.png)
* the ```domain``` in the ```acl``` must match the ```domain``` configured for the Entitlements Service.
![domain](images/10_entitlements_domain.png)
These are just some of the authorization rules. There are also compliance rules that verify, for example, if legal tag has not expired. Compliance rules are out of the scope of this doc.
## Accessing Azure resources
OpenDES services access Azure resources using the following credentials -
* CosmosDB - connection string
* Storage - storage key
Currently in GCP implementation, a shadow identity is created for each end user in GCP, and the service delegate to GCP to authorize storage access based on the shadow identity. We chose the above auth methods for the following reasons -
1. We try to avoid forcing customers who wish to operate the services in multi-tenancy mode to create B2B users or shadow identities. Users should be able to use their existing AAD credential to login.
2. There may be requirements that different parts of a file require different permissions. This can only be achieved in application.
## Summary
Seurity in OpenDES is complex and evolving. This doc summerizes our current state of implementation as of Oct 2019 on Azure MVP. Here are the main takeaways -
* OpenDES services can be multi-tenant or single-tenant in AAD.
* End users can use their existing AAD credential to authenticate. There's no need to create shadow identities in AAD for external users.
* Authorization is implemented in the application rather than using AAD groups or app roles.
* Service to service authentication uses AAD service principals.
* Application authorizes access to Azure resources rather than delegating to Azure RBAC to authorize the user.
* OpenDES tenant serves as a logical isolation for data access rather than a physical isolation of cloud infrastructure.
## License
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](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.
\ No newline at end of file
# os-entitlements-azure
os-entitlements-azure is a [Spring Boot](https://spring.io/projects/spring-boot) service that hosts CRUD APIs that enable management of user entitlements.
## Running Locally
### Requirements
In order to run this service locally, you will need the following:
- [Maven 3.6.0+](https://maven.apache.org/download.cgi)
- [AdoptOpenJDK8](https://adoptopenjdk.net/)
- Infrastructure dependencies, deployable through the relevant [infrastructure template](https://dev.azure.com/slb-des-ext-collaboration/open-data-ecosystem/_git/infrastructure-templates?path=%2Finfra&version=GBmaster&_a=contents)
- While not a strict dependency, example commands in this document use [bash](https://www.gnu.org/software/bash/)
### General Tips
**Environment Variable Management**
The following tools make environment variable configuration simpler
- [direnv](https://direnv.net/) - for a shell/terminal environment
- [EnvFile](https://plugins.jetbrains.com/plugin/7861-envfile) - for [Intellij IDEA](https://www.jetbrains.com/idea/)
**Lombok**
This project uses [Lombok](https://projectlombok.org/) for code generation. You may need to configure your IDE to take advantage of this tool.
- [Intellij configuration](https://projectlombok.org/setup/intellij)
- [VSCode configuration](https://projectlombok.org/setup/vscode)
### Environment Variables
In order to run the service locally, you will need to have the following environment variables defined.
**Note** The following command can be useful to pull secrets from keyvault:
```bash
az keyvault secret show --vault-name $KEY_VAULT_NAME --name $KEY_VAULT_SECRET_NAME --query value -otsv
```
**Required to run service**
| name | value | description | sensitive? | source |
| --- | --- | --- | --- | --- |
| `service_domain_name` | ex `contoso.com` | The name of the domain for which the service will run | no | -- |
| `aad_client_id` | `********` | AAD client application ID | yes | output of infrastructure deployment |
| `azure.activedirectory.AppIdUri` | `api://${azure.activedirectory.client-id}` | URI for AAD Application | no | -- |
| `azure.activedirectory.session-stateless` | `true` | Flag run in stateless mode (needed by AAD dependency) | no | -- |
| `cosmosdb_database` | ex `foo-db` | The name of the CosmosDB database | no | output of infrastructure deployment |
| `KEYVAULT_URI` | ex `https://foo-keyvault.vault.azure.net/` | URI of KeyVault that holds application secrets | no | output of infrastructure deployment |
| `appinsights_key` | `********` | API Key for App Insights | yes | output of infrastructure deployment |
| `spring.application.name` | `entitlements-azure` | Name of application. Needed by App Insights | no | -- |
| `AZURE_CLIENT_ID` | `********` | Identity to run the service locally. This enables access to Azure resources. You only need this if running locally | yes | keyvault secret: `$KEYVAULT_URI/secrets/app-dev-sp-username` |
| `AZURE_TENANT_ID` | `********` | AD tenant to authenticate users from | yes | -- |
| `AZURE_CLIENT_SECRET` | `********` | Secret for `$AZURE_CLIENT_ID` | yes | keyvault secret: `$KEYVAULT_URI/secrets/app-dev-sp-password` |
**Required to run integration tests**
| name | value | description | sensitive? | source |
| --- | --- | --- | --- | --- |
| `ENTITLEMENT_URL` | ex `http://localhost:8080/` | The host where the service is running | no | -- |
| `DOMAIN` | ex `contoso.com` | Must match the value of `service_domain_name` above | no | -- |
| `MY_TENANT` | ex `opendes` | OSDU tenant used for testing | no | -- |
| `INTEGRATION_TESTER` | `********` | System identity to assume for API calls. Note: this user must have entitlements configured already | no | -- |
| `AZURE_TESTER_SERVICEPRINCIPAL_SECRET` | `********` | Secret for `$INTEGRATION_TESTER` | yes | -- |
| `AZURE_AD_TENANT_ID` | `********` | AD tenant to authenticate users from | yes | -- |
| `AZURE_AD_APP_RESOURCE_ID` | `********` | AAD client application ID | yes | output of infrastructure deployment |
| `AZURE_AD_OTHER_APP_RESOURCE_ID` | `********` | AAD client application ID for another application | yes | -- |
| `EXPIRED_TOKEN` | `********` | An expired JWT token | yes | Create one, then wait until it expires |
| `ENTITLEMENT_MEMBER_NAME_INVALID` | ex. `InvalidTestAdmin` | Used for negative testing | no | -- |
| `ENTITLEMENT_MEMBER_NAME_VALID` | `********` | Secret from `$INTEGRATION_TESTER` for userInfo cosmoscollection partitionId | yes | Create in userInfo cosmosCollection |
| `ENTITLEMENT_GROUP_NAME_VALID` | ex. `data.test1` | A group name generated by running integration tests. | no | -- |
### Configure Maven
Check that maven is installed:
```bash
$ mvn --version
Apache Maven 3.6.0
Maven home: /usr/share/maven
Java version: 1.8.0_212, vendor: AdoptOpenJDK, runtime: /usr/lib/jvm/jdk8u212-b04/jre
...
```
You will need to configure access to the remote maven repository that holds the OSDU dependencies. This file should live within `~/.m2/settings.xml`:
```bash
$ cat ~/.m2/settings.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>os-core</id>
<username>mvn-pat</username>
<!-- Treat this auth token like a password. Do not share it with anyone, including Microsoft support. -->
<!-- The generated token expires on or before 11/14/2019 -->
<password>$PERSONAL_ACCESS_TOKEN_GOES_HERE</password>
</server>
</servers>
</settings>
```
### Build and run the application
After configuring your environment as specified above, you can follow these steps to build and run the application
```bash
# execute build + unit tests
$ mvn clean package
...
[INFO] BUILD SUCCESS
# run service
$ java -jar $(find ./target/ -name '*.jar')
```
### Test the application
After the service has started it should be accessible via a web browser by visiting [http://localhost:8080/swagger-ui.html](http://localhost:8080/swagger-ui.html). If the request does not fail, you can then run the integration tests.
```bash
$ mvn clean test -f integration-tests/pom.xml
...
[INFO] BUILD SUCCESS
```
## Debugging
Jet Brains - the authors of Intellij IDEA, have written an [excellent guide](https://www.jetbrains.com/help/idea/debugging-your-first-java-application.html) on how to debug java programs.
## Configuring User Entitlements
As of now, the management APIs that enable user entitlements to be configured are a WIP. Until they are complete, here is how you can configure user entitlements manually.
- Identify the correct CosmosDB account. This can be found in the output of the infrastructure template. Alternatively, you should be able to identify it as the singular CosmosDB account that is provisioned in the resource group that hosts the this service
- Use the `Data Explorer` tool in the CosmosDB UI and navigate to the `UserInfo` container
- Identify if there already exists a `User` record in the table by applying a filter equal to `WHERE c.id = '$IDENTITY_ID'`. `$IDENTITY_ID` will be one of: (1) Object ID of a user (i.e., `974cb327-1406-76b9-91de-cbd6eb7ec949`) or (2) Application ID of a system assigned identity (i.e., `930b212b-e21f-478f-b993-af9b41abe836`)
- If the user exists, verify that the permissions are correct. If the user needs to be added to a group, you can edit the document directly and click `Save`
- If the user does not exist, you can add a document that has the following schema. The exact groups you wish to provision to the user will most likely be different, so be sure to add/remove the appropriate roles. The below listing represents a user with full access to all services.
```json
{
"id": "$IDENTITY_ID",
"tenants": [
{
"name": "$SOME_OSDU_TENANT",
"groups": [
"service.storage.admin",
"service.legal.admin",
"data.datalake.admin",
"data.datalake.viewer",
"data.default.viewer",
"data.default.owner",
"service.search.admin",
"service.search.user",
"data.default.viewers",
"data.default.owners",
"service.entitlements.admin"
]
},
{
"name": "$ANOTHER_OSDU_TENANT",
"groups": [
...
]
}
]
}
```
## Deploying service to Azure
Service deployments into Azure are standardized to make the process the same for all services. The steps to deploy into
Azure can be [found here](https://dev.azure.com/slb-des-ext-collaboration/open-data-ecosystem/_git/infrastructure-templates?path=%2Fdocs%2Fosdu%2FSERVICE_DEPLOYMENTS.md&_a=preview)
## License
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](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.
**Temporary file that needs to be removed before merging this into upstream**
This file contains list of TODO, that still undone and need to be completed before completing with changes in this branch:
1. EntitlementsAzure.java
- TODO: group hierarchical design and implementation.
2. EntitlementsCreateGroupTest.java
- TODO: To clean up new groups and member after each test run, needs to implement DELETE REST method.
- TODO: do we need to validate the group name? in Golang version, there is no group name validation implementation.
\ No newline at end of file
# 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.
trigger:
branches:
include:
- master
paths:
exclude:
- README.md
- OpenDES_Azure_Security.md
- .gitignore
- images/
pool:
vmImage: 'ubuntu-latest'
demands: maven
steps:
- task: Maven@3
inputs:
mavenPomFile: 'pom.xml'
options: '--settings ./maven/settings.xml -DVSTS_FEED_TOKEN=$(VSTS_FEED_TOKEN)'
publishJUnitResults: false
- task: CopyFiles@2
displayName: 'Copy artifacts for maven deploy to: $(build.artifactstagingdirectory)'
inputs:
SourceFolder:
Contents: |
pom.xml
maven/settings.xml
target/*.jar
TargetFolder: '$(build.artifactstagingdirectory)'
- task: ArchiveFiles@2
displayName: 'Archive testing'
inputs:
rootFolderOrFile: integration-tests
archiveFile: '$(Build.ArtifactStagingDirectory)/testing.zip'
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: drop'
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)'
ArtifactName: 'drop'
publishLocation: 'Container'
condition: succeededOrFailed()
\ No newline at end of file
# 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.
#####################
# README: Defines a template that's triggered when either a PR is created or updated. The intent for this
# template is to release the latest service artifact to only the dev integration environment to
# validate the integration tests have passed. You'd typically add this pipeline to your master branch policy.
#####################
trigger:
batch: true
branches:
include:
- master
paths:
exclude:
- /**/*.md
- .gitignore
- images/
pr:
autoCancel: false
branches:
include:
- '*'
exclude:
- master
paths:
exclude:
- /**/*.md
- .gitignore
- images/
variables:
- group: 'Azure Common Secrets'
- group: 'Azure - Common'
- name: serviceName
value: 'entitlements'
resources:
repositories:
- repository: infrastructure-templates
type: git
name: open-data-ecosystem/infrastructure-templates
stages:
- template: devops/service-pipelines/build-stage.yml@infrastructure-templates
parameters:
copyFileContents: |
pom.xml
maven/settings.xml
target/*.jar
copyFileContentsToFlatten: ''
mavenOptions: '--settings ./maven/settings.xml -DVSTS_FEED_TOKEN=$(VSTS_FEED_TOKEN)'
serviceBase: ${{ variables.serviceName }}
testingRootFolder: 'integration-tests'
- template: devops/service-pipelines/deploy-stages.yml@infrastructure-templates
parameters:
serviceName: ${{ variables.serviceName }}
providers:
- name: Azure
# Merges into Master
${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
environments: ['devint', 'qa', 'prod']
# PR updates / creations
${{ if ne(variables['Build.SourceBranchName'], 'master') }}:
environments: ['devint']
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>dev-azure-com-slb-des-ext-collaboration-os-core</id>
<username>os-core</username>
<!-- Treat this auth token like a password. Do not share it with anyone, including Microsoft support. -->
<!-- The generated token expires on or before 11/14/2019 -->
<password>${VSTS_FEED_TOKEN}</password>
</server>
</servers>
</settings>
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment