-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow authentication using RBAC instead of API Keys #427
Changes from 14 commits
6c52e81
bd6fa86
eee14a8
f049d63
97b9937
ad80985
f834699
eca07f1
4b6e7a8
f48cca4
477f162
c3e8a57
c8fdc5f
314aca8
52c460a
dfbd8ad
2e96bfd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
AZURE_AUTH_TYPE= | ||
AZURE_SEARCH_SERVICE= | ||
AZURE_SEARCH_INDEX= | ||
AZURE_SEARCH_KEY= | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,7 @@ def assets(path): | |
SEARCH_TOP_K = os.environ.get("SEARCH_TOP_K", 5) | ||
SEARCH_STRICTNESS = os.environ.get("SEARCH_STRICTNESS", 3) | ||
SEARCH_ENABLE_IN_DOMAIN = os.environ.get("SEARCH_ENABLE_IN_DOMAIN", "true") | ||
AZURE_AUTH_TYPE = os.environ.get("AZURE_AUTH_TYPE", "rbac") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we actually have this default to "keys" and set it to "rbac" from the bicep deployment? The reason to have it default to keys is that for the case of deployment from the Azure OpenAI Studio chat playground, role assignments are not configured by default between the resources, so keys need to be used by default in that scenario. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 Good point! I've just pushed that change. |
||
|
||
# ACS Integration Settings | ||
AZURE_SEARCH_SERVICE = os.environ.get("AZURE_SEARCH_SERVICE") | ||
|
@@ -139,16 +140,22 @@ def assets(path): | |
logging.exception("Exception in CosmosDB initialization", e) | ||
cosmos_conversation_client = None | ||
|
||
def should_use_keys(): | ||
return AZURE_AUTH_TYPE == "keys" | ||
|
||
def get_default_token(): | ||
return DefaultAzureCredential().get_token("https://cognitiveservices.azure.com/.default").token | ||
|
||
def is_chat_model(): | ||
if 'gpt-4' in AZURE_OPENAI_MODEL_NAME.lower() or AZURE_OPENAI_MODEL_NAME.lower() in ['gpt-35-turbo-4k', 'gpt-35-turbo-16k']: | ||
return True | ||
return False | ||
|
||
def should_use_data(): | ||
if AZURE_SEARCH_SERVICE and AZURE_SEARCH_INDEX and AZURE_SEARCH_KEY: | ||
if AZURE_SEARCH_SERVICE and AZURE_SEARCH_INDEX: | ||
if DEBUG_LOGGING: | ||
logging.debug("Using Azure Cognitive Search") | ||
|
||
return True | ||
|
||
if AZURE_COSMOSDB_MONGO_VCORE_DATABASE and AZURE_COSMOSDB_MONGO_VCORE_CONTAINER and AZURE_COSMOSDB_MONGO_VCORE_INDEX and AZURE_COSMOSDB_MONGO_VCORE_CONNECTION_STRING: | ||
|
@@ -242,29 +249,33 @@ def prepare_body_headers_with_data(request): | |
if DEBUG_LOGGING: | ||
logging.debug(f"FILTER: {filter}") | ||
|
||
body["dataSources"].append( | ||
{ | ||
"type": "AzureCognitiveSearch", | ||
"parameters": { | ||
"endpoint": f"https://{AZURE_SEARCH_SERVICE}.search.windows.net", | ||
"key": AZURE_SEARCH_KEY, | ||
"indexName": AZURE_SEARCH_INDEX, | ||
"fieldsMapping": { | ||
"contentFields": parse_multi_columns(AZURE_SEARCH_CONTENT_COLUMNS) if AZURE_SEARCH_CONTENT_COLUMNS else [], | ||
"titleField": AZURE_SEARCH_TITLE_COLUMN if AZURE_SEARCH_TITLE_COLUMN else None, | ||
"urlField": AZURE_SEARCH_URL_COLUMN if AZURE_SEARCH_URL_COLUMN else None, | ||
"filepathField": AZURE_SEARCH_FILENAME_COLUMN if AZURE_SEARCH_FILENAME_COLUMN else None, | ||
"vectorFields": parse_multi_columns(AZURE_SEARCH_VECTOR_COLUMNS) if AZURE_SEARCH_VECTOR_COLUMNS else [] | ||
}, | ||
"inScope": True if AZURE_SEARCH_ENABLE_IN_DOMAIN.lower() == "true" else False, | ||
"topNDocuments": AZURE_SEARCH_TOP_K, | ||
"queryType": query_type, | ||
"semanticConfiguration": AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG if AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG else "", | ||
"roleInformation": AZURE_OPENAI_SYSTEM_MESSAGE, | ||
"filter": filter, | ||
"strictness": int(AZURE_SEARCH_STRICTNESS) | ||
} | ||
}) | ||
cognitiveSearchDataSource = { | ||
sarah-widder marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"type": "AzureCognitiveSearch", | ||
"parameters": { | ||
"endpoint": f"https://{AZURE_SEARCH_SERVICE}.search.windows.net", | ||
"indexName": AZURE_SEARCH_INDEX, | ||
"fieldsMapping": { | ||
"contentFields": parse_multi_columns(AZURE_SEARCH_CONTENT_COLUMNS) if AZURE_SEARCH_CONTENT_COLUMNS else [], | ||
"titleField": AZURE_SEARCH_TITLE_COLUMN if AZURE_SEARCH_TITLE_COLUMN else None, | ||
"urlField": AZURE_SEARCH_URL_COLUMN if AZURE_SEARCH_URL_COLUMN else None, | ||
"filepathField": AZURE_SEARCH_FILENAME_COLUMN if AZURE_SEARCH_FILENAME_COLUMN else None, | ||
"vectorFields": parse_multi_columns(AZURE_SEARCH_VECTOR_COLUMNS) if AZURE_SEARCH_VECTOR_COLUMNS else [] | ||
}, | ||
"inScope": True if AZURE_SEARCH_ENABLE_IN_DOMAIN.lower() == "true" else False, | ||
"topNDocuments": AZURE_SEARCH_TOP_K, | ||
"queryType": query_type, | ||
"semanticConfiguration": AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG if AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG else "", | ||
"roleInformation": AZURE_OPENAI_SYSTEM_MESSAGE, | ||
"filter": filter, | ||
"strictness": int(AZURE_SEARCH_STRICTNESS) | ||
} | ||
} | ||
|
||
if should_use_keys(): | ||
cognitiveSearchDataSource["parameters"]["key"] = AZURE_SEARCH_KEY | ||
|
||
body["dataSources"].append(cognitiveSearchDataSource) | ||
|
||
elif DATASOURCE_TYPE == "AzureCosmosDB": | ||
# Set query type | ||
query_type = "vector" | ||
|
@@ -352,9 +363,13 @@ def prepare_body_headers_with_data(request): | |
|
||
headers = { | ||
'Content-Type': 'application/json', | ||
'api-key': AZURE_OPENAI_KEY, | ||
"x-ms-useragent": "GitHubSampleWebApp/PublicAPI/3.0.0" | ||
} | ||
|
||
if should_use_keys(): | ||
headers["api-key"] = AZURE_OPENAI_KEY | ||
else: | ||
headers["Authorization"] = "Bearer " + get_default_token() | ||
|
||
return body, headers | ||
|
||
|
@@ -539,7 +554,11 @@ def conversation_without_data(request_body): | |
openai.api_type = "azure" | ||
openai.api_base = AZURE_OPENAI_ENDPOINT if AZURE_OPENAI_ENDPOINT else f"https://{AZURE_OPENAI_RESOURCE}.openai.azure.com/" | ||
openai.api_version = "2023-08-01-preview" | ||
openai.api_key = AZURE_OPENAI_KEY | ||
|
||
if should_use_keys(): | ||
openai.api_key = AZURE_OPENAI_KEY | ||
else: | ||
openai.api_key = + get_default_token() | ||
|
||
request_messages = request_body["messages"] | ||
messages = [ | ||
|
@@ -861,7 +880,12 @@ def generate_title(conversation_messages): | |
openai.api_type = "azure" | ||
openai.api_base = base_url | ||
openai.api_version = "2023-03-15-preview" | ||
openai.api_key = AZURE_OPENAI_KEY | ||
|
||
if should_use_keys(): | ||
openai.api_key = AZURE_OPENAI_KEY | ||
else: | ||
openai.api_key = get_default_token() | ||
|
||
completion = openai.ChatCompletion.create( | ||
engine=AZURE_OPENAI_MODEL, | ||
messages=messages, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,9 @@ resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = { | |
publicNetworkAccess: publicNetworkAccess | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you do a PR to update this too? https://github.com/Azure/azure-dev/blob/main/templates/common/infra/bicep/core/ai/cognitiveservices.bicep There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What updates are required? Do you just mean to pull from main? |
||
} | ||
sku: sku | ||
identity: { | ||
type: 'SystemAssigned' | ||
} | ||
} | ||
|
||
@batchSize(1) | ||
|
@@ -41,3 +44,5 @@ output id string = account.id | |
output name string = account.name | ||
output skuName string = account.sku.name | ||
output key string = account.listKeys().key1 | ||
output identityPrincipalId string = account.identity.principalId | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should match the name of the other core files when they provide an id, which is just principalId |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ param sku object = { | |
|
||
param authOptions object = {} | ||
param semanticSearch string = 'disabled' | ||
param authType string = 'rbac' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you do a PR to update this? https://github.com/Azure/azure-dev/blob/main/templates/common/infra/bicep/core/search/search-services.bicep There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What updates are required? Do you just mean to pull from main? |
||
|
||
resource search 'Microsoft.Search/searchServices@2021-04-01-preview' = { | ||
name: name | ||
|
@@ -17,8 +18,8 @@ resource search 'Microsoft.Search/searchServices@2021-04-01-preview' = { | |
type: 'SystemAssigned' | ||
} | ||
properties: { | ||
authOptions: authOptions | ||
disableLocalAuth: false | ||
authOptions: authType == 'keys' ? authOptions : null | ||
disableLocalAuth: authType == 'keys' ? false : true | ||
disabledDataExfiltrationOptions: [] | ||
encryptionWithCmk: { | ||
enforcement: 'Unspecified' | ||
|
@@ -41,3 +42,4 @@ output endpoint string = 'https://${name}.search.windows.net/' | |
output name string = search.name | ||
output skuName string = sku.name | ||
output adminKey string = search.listAdminKeys().primaryKey | ||
output identityPrincipalId string = search.identity.principalId | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment about name principalId |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,12 @@ param appServicePlanName string = '' | |
param backendServiceName string = '' | ||
param resourceGroupName string = '' | ||
|
||
@allowed([ | ||
'keys' | ||
'rbac' | ||
]) | ||
param authType string = 'rbac' | ||
|
||
param searchServiceName string = '' | ||
param searchServiceResourceGroupName string = '' | ||
param searchServiceResourceGroupLocation string = location | ||
|
@@ -115,10 +121,11 @@ module backend 'core/host/appservice.bicep' = { | |
authClientId: authClientId | ||
authIssuerUri: authIssuerUri | ||
appSettings: { | ||
AZURE_AUTH_TYPE: authType | ||
// search | ||
AZURE_SEARCH_INDEX: searchIndexName | ||
AZURE_SEARCH_SERVICE: searchService.outputs.name | ||
AZURE_SEARCH_KEY: searchService.outputs.adminKey | ||
AZURE_SEARCH_KEY: authType == 'keys' ? searchService.outputs.adminKey : null | ||
AZURE_SEARCH_USE_SEMANTIC_SEARCH: searchUseSemanticSearch | ||
AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG: searchSemanticSearchConfig | ||
AZURE_SEARCH_TOP_K: searchTopK | ||
|
@@ -131,7 +138,7 @@ module backend 'core/host/appservice.bicep' = { | |
AZURE_OPENAI_RESOURCE: openAi.outputs.name | ||
AZURE_OPENAI_MODEL: openAIModel | ||
AZURE_OPENAI_MODEL_NAME: openAIModelName | ||
AZURE_OPENAI_KEY: openAi.outputs.key | ||
AZURE_OPENAI_KEY: authType == 'keys' ? openAi.outputs.key : null | ||
AZURE_OPENAI_TEMPERATURE: openAITemperature | ||
AZURE_OPENAI_TOP_P: openAITopP | ||
AZURE_OPENAI_MAX_TOKENS: openAIMaxTokens | ||
|
@@ -189,6 +196,7 @@ module searchService 'core/search/search-services.bicep' = { | |
aadAuthFailureMode: 'http401WithBearerChallenge' | ||
} | ||
} | ||
authType: authType | ||
sku: { | ||
name: !empty(searchServiceSkuName) ? searchServiceSkuName : 'standard' | ||
} | ||
|
@@ -250,6 +258,27 @@ module searchServiceContribRoleUser 'core/security/role.bicep' = { | |
} | ||
} | ||
|
||
|
||
module openAiContributorRoleUser 'core/security/role.bicep' = if (authType == 'rbac') { | ||
scope: searchServiceResourceGroup | ||
name: 'openai-contributor-role-user' | ||
params: { | ||
principalId: principalId | ||
roleDefinitionId: 'a001fd3d-188f-4b5d-821b-7da978bf7442' | ||
principalType: 'User' | ||
} | ||
} | ||
|
||
module cognitiveServicesContributorRoleUser 'core/security/role.bicep' = if (authType == 'rbac') { | ||
scope: searchServiceResourceGroup | ||
name: 'cognitive-services-contributor-role-user' | ||
params: { | ||
principalId: principalId | ||
roleDefinitionId: '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68' | ||
principalType: 'User' | ||
} | ||
} | ||
|
||
// SYSTEM IDENTITIES | ||
module openAiRoleBackend 'core/security/role.bicep' = { | ||
scope: openAiResourceGroup | ||
|
@@ -271,6 +300,46 @@ module searchRoleBackend 'core/security/role.bicep' = { | |
} | ||
} | ||
|
||
module searchRoleOpenAi 'core/security/role.bicep' = if (authType == 'rbac') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any new roles you create will also need to be assigned to the user's principalId, see "user roles" above. |
||
scope: searchServiceResourceGroup | ||
name: 'search-role-openai' | ||
params: { | ||
principalId: openAi.outputs.identityPrincipalId | ||
roleDefinitionId: '1407120a-92aa-4202-b7e9-c0e197c71c8f' | ||
principalType: 'ServicePrincipal' | ||
} | ||
} | ||
|
||
module searchServiceRoleOpenAi 'core/security/role.bicep' = if (authType == 'rbac') { | ||
scope: searchServiceResourceGroup | ||
name: 'search-service-role-openai' | ||
params: { | ||
principalId: openAi.outputs.identityPrincipalId | ||
roleDefinitionId: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' | ||
principalType: 'ServicePrincipal' | ||
} | ||
} | ||
|
||
module openAiContributorRoleSearch 'core/security/role.bicep' = if (authType == 'rbac') { | ||
scope: searchServiceResourceGroup | ||
name: 'openai-contributor-role-search' | ||
params: { | ||
principalId: searchService.outputs.identityPrincipalId | ||
roleDefinitionId: 'a001fd3d-188f-4b5d-821b-7da978bf7442' | ||
principalType: 'ServicePrincipal' | ||
} | ||
} | ||
|
||
module cognitiveServicesContributorRoleSearch 'core/security/role.bicep' = if (authType == 'rbac') { | ||
scope: searchServiceResourceGroup | ||
name: 'cognitive-services-contributor-role-search' | ||
params: { | ||
principalId: searchService.outputs.identityPrincipalId | ||
roleDefinitionId: '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68' | ||
principalType: 'ServicePrincipal' | ||
} | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In outputs below we shouldn't be outputting keys. You will get a bicep linter warning. Best to ask the user to manually set them with in env vars at this time until we have a secure way to store them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Phew, glad to see the keys are out of this output finally! That's been a TODO (in my head) for a long time. Another option btw is to provide a script that grabs the keys, which is what I've had to do for another repo which unfortunately requires keys: But probably okay not to do that here, since we now have the keyless option. |
||
// For doc prep | ||
module docPrepResources 'docprep.bicep' = { | ||
name: 'docprep-resources${resourceToken}' | ||
|
@@ -297,7 +366,6 @@ output AZURE_SEARCH_INDEX string = searchIndexName | |
output AZURE_SEARCH_SERVICE string = searchService.outputs.name | ||
output AZURE_SEARCH_SERVICE_RESOURCE_GROUP string = searchServiceResourceGroup.name | ||
output AZURE_SEARCH_SKU_NAME string = searchService.outputs.skuName | ||
output AZURE_SEARCH_KEY string = searchService.outputs.adminKey | ||
output AZURE_SEARCH_USE_SEMANTIC_SEARCH bool = searchUseSemanticSearch | ||
output AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG string = searchSemanticSearchConfig | ||
output AZURE_SEARCH_TOP_K int = searchTopK | ||
|
@@ -314,7 +382,6 @@ output AZURE_OPENAI_ENDPOINT string = openAi.outputs.endpoint | |
output AZURE_OPENAI_MODEL string = openAIModel | ||
output AZURE_OPENAI_MODEL_NAME string = openAIModelName | ||
output AZURE_OPENAI_SKU_NAME string = openAi.outputs.skuName | ||
output AZURE_OPENAI_KEY string = openAi.outputs.key | ||
output AZURE_OPENAI_EMBEDDING_NAME string = '${embeddingDeploymentName}' | ||
output AZURE_OPENAI_TEMPERATURE int = openAITemperature | ||
output AZURE_OPENAI_TOP_P int = openAITopP | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather the flow of the application take care of all the settings and not require the user to take manual action.
We are experimenting with adding an AZURE_AUTH_TYPE parameter that can get set to rbac or keys and the bicep does the appropriate thing based on that.
It's not finalized, but should be soon.