diff --git a/.github/workflows/E2E.yml b/.github/workflows/E2E.yml
index 24fe7a6..a62a389 100644
--- a/.github/workflows/E2E.yml
+++ b/.github/workflows/E2E.yml
@@ -52,13 +52,37 @@ jobs:
- uses: actions/checkout@v2
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v2
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.IBERSANO_DOCKER_USERNAME }}
+ password: ${{ secrets.IBERSANO_DOCKER_TOKEN }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: |
+ ${{ vars.docker_repo }}/easy-auth-proxy
+
+ - name: Build and push Docker images
+ uses: docker/build-push-action@v3
+ with:
+ context: .
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+
- name: Call the script
continue-on-error: true
run: |
- bash main.sh -a "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -c "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -r "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -e ${{ vars.email }} -l ${{ vars.location }}
+ bash main.sh -i ${{ vars.imageName }} -a "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -c "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -r "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -e ${{ vars.email }} -l ${{ vars.location }}
- name: Delete e2e environment
- if: ${{ vars.DeleteOnFailure == 'true' }} || success()
+ if: ${{ vars.DeleteOnFailure == 'true' }}
run: |
if [ $(az group exists --name ${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}) == "true" ]; then
az group delete -n ${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }} --yes
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ff80223..55bea52 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -62,13 +62,13 @@ jobs:
uses: actions/checkout@v2
- name: Log in to Docker Hub
- uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
+ uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USER_ID }}
password: ${{ secrets.DOCKER_REGISTRY_PASS }}
- name: Log in to the Container registry
- uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
+ uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -76,14 +76,14 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
- uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
+ uses: docker/metadata-action@v4
with:
images: |
ghcr.io/${{ github.repository }}/easy-auth-proxy
easyauthfork8s/easy-auth-proxy
- name: Build and push Docker images
- uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
+ uses: docker/build-push-action@v3
with:
context: .
push: true
diff --git a/AutomationScripts/3-registerAADApp.sh b/AutomationScripts/3-registerAADApp.sh
index b41b348..c4059d5 100644
--- a/AutomationScripts/3-registerAADApp.sh
+++ b/AutomationScripts/3-registerAADApp.sh
@@ -18,7 +18,7 @@ if [ -n "$ALT_TENANT_ID" ]; then
fi
fi
-CLIENT_ID=$(az ad app create --display-name $AD_APP_NAME --web-home-page-url $HOMEPAGE --web-redirect-uris $REPLY_URLS --required-resource-accesses @./TemplateFiles/manifest.json -o json | jq -r '.appId')
+CLIENT_ID=$(az ad app create --display-name $AD_APP_NAME --web-home-page-url $HOMEPAGE --web-redirect-uris $REPLY_URLS --required-resource-accesses @./TemplateFiles/manifest.json --optional-claims @./TemplateFiles/claims.json -o json | jq -r '.appId')
echo "CLIENT_ID: " $CLIENT_ID
# AAD core store is eventually consistent. Usually we can retrieve the object on the first try after creation,
diff --git a/TemplateFiles/claims.json b/TemplateFiles/claims.json
new file mode 100644
index 0000000..e1daa85
--- /dev/null
+++ b/TemplateFiles/claims.json
@@ -0,0 +1,10 @@
+{
+ "idToken": [
+ {
+ "name": "login_hint",
+ "essential": false
+ }
+ ],
+ "accessToken": [],
+ "saml2Token": []
+}
\ No newline at end of file
diff --git a/charts/easyauth-proxy/templates/statefulset.yaml b/charts/easyauth-proxy/templates/statefulset.yaml
index b8114b0..d8d6186 100644
--- a/charts/easyauth-proxy/templates/statefulset.yaml
+++ b/charts/easyauth-proxy/templates/statefulset.yaml
@@ -57,13 +57,19 @@ spec:
value: "{{ .Values.easyAuthForK8s.dataProtectionFileLocation }}"
- name: EasyAuthForK8s__SigninPath
value: "{{ .Values.basePath }}{{ .Values.easyAuthForK8s.signinPath }}"
+ - name: EasyAuthForK8s__SignoutPath
+ value: "{{ .Values.basePath }}{{ .Values.easyAuthForK8s.signoutPath }}"
- name: EasyAuthForK8s__AuthPath
value: "{{ .Values.basePath }}{{ .Values.easyAuthForK8s.authPath }}"
- name: EasyAuthForK8s__AllowBearerToken
value: "{{ .Values.easyAuthForK8s.allowBearerToken }}"
- name: EasyAuthForK8s__DefaultRedirectAfterSignin
value: "{{ .Values.easyAuthForK8s.defaultRedirectAfterSignin }}"
- name: EasyAuthForK8s__CompressCookieClaims
+ - name: EasyAuthForK8s__DefaultRedirectAfterSignout
+ value: "{{ .Values.easyAuthForK8s.defaultRedirectAfterSignout }}"
+ - name: EasyAuthForK8s__SignedOutNoRedirectPath
+ value: "{{ .Values.basePath }}/signedout"
+ - name: EasyAuthForK8s__CompressCookieClaims
value: "{{ .Values.easyAuthForK8s.compressCookieClaims }}"
- name: EasyAuthForK8s__ResponseHeaderPrefix
value: "{{ .Values.easyAuthForK8s.responseHeaderPrefix }}"
@@ -81,8 +87,6 @@ spec:
value: "{{ .Values.azureAd.clientId }}"
- name: AzureAd__CallbackPath
value: "{{ .Values.basePath }}{{ .Values.azureAd.callbackPath }}"
- - name: AzureAd__SignedOutCallbackPath
- value: "{{ .Values.basePath }}{{ .Values.azureAd.signedOutCallbackPath }}"
- name: AzureAd__SignUpSignInPolicyId
value: "{{ .Values.azureAd.signUpSignInPolicyId }}"
- name: AzureAd__ClientSecret
diff --git a/charts/easyauth-proxy/values.yaml b/charts/easyauth-proxy/values.yaml
index 3faf968..12729af 100644
--- a/charts/easyauth-proxy/values.yaml
+++ b/charts/easyauth-proxy/values.yaml
@@ -4,6 +4,12 @@
tlsSecretName: ""
appHostName: ""
+
+# The base path will be pre-pended to all urls on the EasyAuth Proxy
+# For example, the Auth endpoint will listen on "/easyauth/auth" by default
+# You should change the base path if you are deploying multiple EasyAuth pods
+# that share the same host name, such as a mult-tenant app. See documentation
+# for more.
basePath: "/easyauth"
replicaCount: 2
@@ -98,28 +104,32 @@ azureAd:
tenantId: ""
# app Id of the service principal.
clientId: ""
+ # B2C Sign-in policy, if used
+ # Leave this blank if not using B2C
+ signUpSignInPolicyId: ""
# configure paths for OIDC middleware
- # there's no reason to change these unless there is a conflict
- # such as another easyauth proxy using the same host name
+ # you shouldn't need to change these from the default
+ # all paths will be prefiixed with the basePath value
callbackPath: "/signin-oidc"
- signedOutCallbackPath : "/signout-callback-oidc"
- # Leave this blank if not B2C
- signUpSignInPolicyId: ""
easyAuthForK8s:
# data protection key ring location
dataProtectionFileLocation: "/mnt/dp"
# configure paths for EasyAuth middleware
- # there's no reason to change these unless there is a conflict
- # such as another easyauth proxy using the same host name
+ # you shouldn't need to change these from the default
+ # all paths will be prefiixed with the basePath value
signinPath: "/login"
authPath: "/auth"
+ signoutPath: "/logout"
# use bearer token as a fall back for cookies
# normally for API web applications only
allowBearerToken: "false"
# fallback path to redirect user after signin if
# prior page url cannot be determined
defaultRedirectAfterSignin: "/"
+ # fallback path to redirect user after signout
+ # if no redirect parameter is provided
+ defaultRedirectAfterSignout: "_blank"
# Make the cookie payload as small as possible to avoid having to
# increase the allowed nginx header size.
compressCookieClaims: "true"
diff --git a/docs/configuration.md b/docs/configuration.md
index c57c30b..3bffd97 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -16,15 +16,17 @@ Here's a list of possible configuration options for the EasyAuth Proxy, which yo
| azureAd | domain | Optional. If your users are internal organizational accounts from a single tenant domain, this can be helpful by providing a "hint" during login to help ensure that the user logs in with the appropriate user account|
| azureAd | tenantId | If you are using the setup script, this value will be determined at runtime and filled in for you. Otherwise, this is the GUID tenant identifier for the Azure AD tenant you want to use. See [How to find my tenant id](https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant)|
| azureAd | clientId | If you are using the setup script, this value will be determined at runtime and filled in for you. Otherwise, this is the GUID application identifier for the Azure AD app registration you want to use. See [App Registrations](https://docs.microsoft.com/en-us/graph/auth-register-app-v2)|
-| azureAd | callbackPath | The path that Open Id Connect messages will be returned from Azure AD. In the majority of cases, you should never need to change this. See [Advanced Scenarios](docs/scenarios.md)|
-| azureAd | signedOutCallbackPath | Reserved for future use - Not currently used|
| azureAd | signUpSignInPolicyId | For B2C only. This is the name of the policy that should be used. Otherwise, leave blank.|
+| azureAd | callbackPath | The path that Open Id Connect messages will be returned from Azure AD. In the majority of cases, you should never need to change this. This configurationoption may be removed in the future. See [Advanced Scenarios](docs/scenarios.md)|
+| azureAd | signedOutCallbackPath | The path that the user will be redirected after clearing the session with Azure AD. It is not recommended that you change this. This configuration option may be removed in the future. See [Advanced Scenarios](docs/scenarios.md)|
| azureAd | clientSecretKeyRefName, clientSecretKeyRefKey | Secret container and key for the client secret. Do not change these or set them directly or store the secret in a yaml file. Rather, provide your secret to helm via the command line via *--set secret.azureclientsecret=$CLIENT_SECRET* |
| easyAuthForK8s | dataProtectionFileLocation | data protection key ring location. |
| easyAuthForK8s | signinPath | The path that the proxy host will respond to sign-in requests. The default should not need to be changed, except for in [Advanced Scenarios](docs/scenarios.md). Note that when changing this value, you must also update the *nginx.ingress.kubernetes.io/auth-signin* annotation in your ingresses to match. |
+| easyAuthForK8s | signoutPath | The path that the proxy use to sign out a user. The default should not need to be changed, except for in [Advanced Scenarios](docs/scenarios.md). |
| easyAuthForK8s | authPath | The path that the proxy host will respond to auth requests. The default should not need to be changed, except for in [Advanced Scenarios](docs/scenarios.md). Note that when changing this value, you must also update the *nginx.ingress.kubernetes.io/auth-url* annotation in your ingresses to match. |
| easyAuthForK8s | allowBearerToken | Default is "false". If "true" this will allow bearer tokens to be used in addition to cookies. Primarily for API callers. |
| easyAuthForK8s | defaultRedirectAfterSignin | This is the final fallback url that the user will be routed to after succesfully logging in. Depending on your nginx configuration, the primary redirect preference will be the path provided by the "rd" query parameter, followed by the url that the user was originally trying to access. This option provides a tertiary and final fallback, with "/" being the default |
+| easyAuthForK8s | defaultRedirectAfterSignout | This is the fallback url that the user will be routed to after logging out. This should be a page that allows anonymous access, otherwise it will result in another log in challenge that results in the user being signed in again. If you don't have page that allows anonymous access, you can remove this variable or set it to a value of "_blank" to configure EasyAuth to render a basic page for you. |
| easyAuthForK8s | compressCookieClaims | Option is "true" by default, set "false" to disable. Experimental feature that serializes, compresses, and encodes the payload of non-essential claims to keep the cookie size as small as possible. This helps to avoid increasing the nginx header buffers beyond the default settings and reduces the size of the data sent from the client with each request.
**WARNING!**: *This feature may introduce a security vulnerability, although no specific vulnerability is known at this time. CRIME, a well-known exploit, takes advantage of compressed streams to decrypt data, however it requires the attacker to be able to introduce arbitrary data into the stream and observe its compressed state. For this feature we are only compressing a portion of the payload which an attacker should not be able to manipulate, so it should be safe in theory. To mitigate any potential concerns, avoid sending sensitive data to the back-end service, or disable this feature.* |
| easyAuthForK8s | responseHeaderPrefix | Prefix for all user information headers. Default is *"x-injected-"*. There is no reason to change this unless you have multiple EasyAuth proxies protecting the same backend and need to discern the source of the headers. |
| easyAuthForK8s | claimEncodingMethod | Default is *UrlEncode*, which should work for most situations. Valid values are:
*UrlEncode*: Invalid characters are escaped according to IETF RFC 3986
*Base64*: The full string value is encoded from UTF-8 bytes to base64 text
*None*: Value is not encoded, and the original string value is sent. This may cause errors for downstream web servers, especially on older platforms
*NoneWithReject*: No encoding is applied, but any header value containing an unsafe character is rejected, and the value "encoding_error" is sent in its place
|
@@ -81,4 +83,5 @@ Notes:
- Not all graph queries work with all types of users, since many graph resources are dependent on the various product licenses that are assigned to the user. If a query raises errors, an `error` property will be returned along with a message.
- Finally, graph queries are run against the Azure AD tenant that EasyAuth is configured to use, which is not necessarily a particular user's home tenant. For example, let's say EasyAuth is configured to use the "Contoso" tenant, which contains a B2B Guest user from the "Fabrikam" tenant. You utilize a graph query that looks for groups users belong to. In this case the results returned for the B2B user will be their "Contoso" group memberships, not groups they belong to in their home "Fabrikam" tenant.
-
+# Implementing Sign-out functionality for your protected applications
+For web applications, EasyAuth can sign a user out of both the cookie session and Azure AD. To sign out a user, you'll need to redirect them to the `signoutPath`, for which the default value is `"/easyauth/logout"`, by providing a link or a button in your application. You may optionally provide a url within your application to return them to after signing out by either setting the `defaultRedirectAfterSignout` value in the helm chart, or by setting the `rd` query string parameter. An example of a link you direct the user to my look like `"/easyauth/logout?rd=/signedout.html"`. If you don't provide a redirect url by setting either of these options, EasyAuth will render a basic page for you.
diff --git a/docs/scenarios.md b/docs/scenarios.md
index 3cdd442..ac6a800 100644
--- a/docs/scenarios.md
+++ b/docs/scenarios.md
@@ -1,2 +1,41 @@
# Advanced Scenarios
-Yikes! We haven't had time to complete this doc yet. We are working on it, so check back later for some interesting ways to configure EasyAuth.
\ No newline at end of file
+
+## Multi-tenant apps
+For applications that need to support multiple Azure AD tenants independently, you can configure and deploy multiple EasyAuth pods. As long as you can distinguish different tenants with ingress rules, you will be able to route auth requests to the correct pod.
+
+For example, let's say you have an application with the url "https://mysharedapp.constoso.com/". This app is a multitenant evironment, where the base url path identifies the tenant within the application ("https://mysharedapp.constoso.com/fabrikam). Configure the helm chart values of each EasyAuth pod with a unique `basePath`, so that the ingress rules can route auth requests to the correct pod. Assuming we use "fabrikam" as the basePath for our sample tenant, your ingress configuration would look something like:
+
+```
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: easyauth-fabrikam-tenant
+ annotations:
+ nginx.ingress.kubernetes.io/auth-url: "https://$host/fabrikam/auth"
+ nginx.ingress.kubernetes.io/auth-signin: "https://$host/fabrikam/login"
+ nginx.ingress.kubernetes.io/auth-response-headers: "x-injected-userinfo,x-injected-name,x-injected-oid,x-injected-preferred-username,x-injected-sub,x-injected-tid,x-injected-email,x-injected-groups,x-injected-scp,x-injected-roles,x-injected-graph"
+ cert-manager.io/cluster-issuer: {{your-cert-manager}}
+
+spec:
+ ingressClassName: nginx
+ tls:
+ - hosts:
+ - {{APP_HOSTNAME}}
+ secretName: {{TLS_SECRET_NAME}}
+ rules:
+ - host: {{APP_HOSTNAME}}
+ http:
+ paths:
+ - path: /fabrikam
+ pathType: Prefix
+ backend:
+ service:
+ name: mysharedapp-pod
+ port:
+ number: 80
+```
+
+
+You will also need to update your Azure AD App Registration (or create a new one) to include the OIDC reply url for the fabrikam EasyAuth pod. The url will be in the form of `https://host/{{baseUrl}}/{{azureAd.callbackPath}}`, which in this case would be "https://mysharedapp.constoso.com/fabrikam//signin-oidc". See [Add a redirect URI](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#add-a-redirect-uri) for more information.
+
+Finally, you will need to update the helm chart values to reflect fabrikam's Azure AD tenant settings. At a miminum, you'll need to set `azureAd.tenantId` to the GUID Id of fabrikam's Azure AD tenant, as well as the `azureAd.domain` value (not required, but provides the best user experience). If you are sharing the same App Registration among EasyAuth pods, the `clientId` value will be the same. In all cases where the App Registration is configured in a tenant that is different than the `azureAd.tenantId` value, you'll need to ensure that the App Registraion is [Multitenant](https://learn.microsoft.com/en-us/azure/active-directory/develop/single-and-multi-tenant-apps).
\ No newline at end of file
diff --git a/sample/EasyAuthForK8s.Sample/Pages/Shared/_Layout.cshtml b/sample/EasyAuthForK8s.Sample/Pages/Shared/_Layout.cshtml
index 3a41d07..a3ce111 100644
--- a/sample/EasyAuthForK8s.Sample/Pages/Shared/_Layout.cshtml
+++ b/sample/EasyAuthForK8s.Sample/Pages/Shared/_Layout.cshtml
@@ -32,7 +32,7 @@
Graph Query