semaphoreui / semaphore

Modern UI and powerful API for Ansible, Terraform, OpenTofu, PowerShell and other DevOps tools.
https://semaphoreui.com
MIT License
10.73k stars 1.07k forks source link

Help with OIDC Azure AD configuration/debugging #1434

Open danieladarve opened 1 year ago

danieladarve commented 1 year ago

Hello everyone,

I am running a dpkg install of Ansible Semaphore v2.8.92 in ubuntu 22.04 behind a nginx reverse proxy configured as suggested in the security section

I am able to click on the SSO button, login into microsoft but when the user is redirected back to the oidc return url, nothing happens, the page just seems to reload and it removes all parameters from the URL... I have tested using a different return url and i can see the token being returned in the parameters.

https://ansible-semaphore.test/api/auth/oidc/provider-name/redirect/?code=XXXXX&state=XXXXX&session_state=XXXXX

Here is what my oidc_providers config looks like:

"oidc_providers": {
        "provider-name": {
            "display_name": "Sign in with Provider",
            "provider_url": "https://login.microsoftonline.com/XXXXX/v2.0",
            "client_id": "XXXXX",
            "client_secret": "XXXXX",
            "scopes": ["openid", "profile", "email"],
            "username_claim": "preferred_username",
            "name_claim": "preferred_username",
            "email_claim": "email"
        }
    }

I can see the correct scopes, and claims Here is what my /v2.0/.well-known/openid-configuration looks like:

{
  "token_endpoint": "https://login.microsoftonline.com/xxxxx/oauth2/v2.0/token",
  "token_endpoint_auth_methods_supported": [
    "client_secret_post",
    "private_key_jwt",
    "client_secret_basic"
  ],
  "jwks_uri": "https://login.microsoftonline.com/xxxxx/discovery/v2.0/keys",
  "response_modes_supported": [
    "query",
    "fragment",
    "form_post"
  ],
  "subject_types_supported": [
    "pairwise"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "response_types_supported": [
    "code",
    "id_token",
    "code id_token",
    "id_token token"
  ],
  "scopes_supported": [
    "openid",
    "profile",
    "email",
    "offline_access"
  ],
  "issuer": "https://login.microsoftonline.com/xxxxx/v2.0",
  "request_uri_parameter_supported": false,
  "userinfo_endpoint": "https://graph.microsoft.com/oidc/userinfo",
  "authorization_endpoint": "https://login.microsoftonline.com/xxxxx/oauth2/v2.0/authorize",
  "device_authorization_endpoint": "https://login.microsoftonline.com/xxxxx/oauth2/v2.0/devicecode",
  "http_logout_supported": true,
  "frontchannel_logout_supported": true,
  "end_session_endpoint": "https://login.microsoftonline.com/xxxxx/oauth2/v2.0/logout",
  "claims_supported": [
    "sub",
    "iss",
    "cloud_instance_name",
    "cloud_instance_host_name",
    "cloud_graph_host_name",
    "msgraph_host",
    "aud",
    "exp",
    "iat",
    "auth_time",
    "acr",
    "nonce",
    "preferred_username",
    "name",
    "tid",
    "ver",
    "at_hash",
    "c_hash",
    "email"
  ],
  "kerberos_endpoint": "https://login.microsoftonline.com/xxxxx/kerberos",
  "tenant_region_scope": "OC",
  "cloud_instance_name": "microsoftonline.com",
  "cloud_graph_host_name": "graph.windows.net",
  "msgraph_host": "graph.microsoft.com",
  "rbac_url": "https://pas.windows.net"
}

Ansible Semaphore Config

{
    "mysql": {
        "host": "127.0.0.1:3306",
        "user": "XXXXX",
        "pass": "XXXXX",
        "name": "ansible",
        "options": null
    },
    "bolt": {
        "host": "",
        "user": "",
        "pass": "",
        "name": "",
        "options": null
    },
    "postgres": {
        "host": "",
        "user": "",
        "pass": "",
        "name": "",
        "options": null
    },
    "dialect": "mysql",
    "port": "",
    "interface": "",
    "tmp_path": "/tmp/semaphore",
    "cookie_hash": "XXXXX",
    "cookie_encryption": "XXXXX",
    "access_key_encryption": "XXXXX",
    "email_sender": "",
    "email_host": "",
    "email_port": "",
    "email_username": "",
    "email_password": "",
    "web_host": "",
    "ldap_binddn": "",
    "ldap_bindpassword": "",
    "ldap_server": "",
    "ldap_searchdn": "",
    "ldap_searchfilter": "",
    "ldap_mappings": {
        "dn": "",
        "mail": "",
        "uid": "",
        "cn": ""
    },
    "telegram_chat": "",
    "telegram_token": "",
    "slack_url": "",
    "max_parallel_tasks": 0,
    "email_alert": false,
    "email_secure": false,
    "telegram_alert": false,
    "slack_alert": false,
    "ldap_enable": false,
    "ldap_needtls": false,
    "ssh_config_path": "",
    "demo_mode": false,
    "git_client": "",
    "password_login_disable": true,
    "oidc_providers": {
        "snooze": {
            "display_name": "Sign in with Provider",
            "provider_url": "https://login.microsoftonline.com/XXXXX/v2.0",
            "client_id": "XXXXX",
            "client_secret": "XXXXX",
            "scopes": ["openid", "profile", "email"],
            "username_claim": "preferred_username",
            "name_claim": "preferred_username",
            "email_claim": "email"
        }
    }
}

Nginx Configuration

upstream semaphore {
    server 127.0.0.1:3000;
}

server {
    client_max_body_size 0;
    chunked_transfer_encoding on;

    server_name subdomain.website.com;

    location / {
        proxy_pass http://semaphore/;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_request_buffering off;
    }

    location /api/ws {
        proxy_pass http://semaphore/api/ws;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Origin "";
    }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/subdomain.website.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/subdomain.website.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

server {
    if ($host = subdomain.website.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80 default_server;
    listen [::]:80 default_server;

    server_name subdomain.website.com;
    return 404; # managed by Certbot

}

In the Azure side I have tested with both

  1. ID tokens (used for implicit and hybrid flows)
  2. Access tokens (used for implicit flows)

I was wondering if anyone has had success configuring Azure AD and could point me in the right direction. Additionally, I'd like to understand if there's a way for me to debug what happens after the user is redirected and lands back on the Semaphore redirect URL.

Thank you!

bilalbayasut commented 1 year ago

faced exactly same issue with you @danieladarve , any updates regarding this ? , can you check in your ends the logs

sudo cat /var/log/syslog | grep semaphore

I've checked mine, but I got

level=error msg="404 Not Found: "

which is strange, eventhough my semaphore run behind port-forwarding, should be no issue re that

ocient-jlarson commented 1 year ago

Very similar issue but with Okta.

Loigzorn commented 1 year ago
level=error msg="404 Not Found: "

Facing a similar issue with Authentik.

xfact-joseph-p commented 1 year ago

I was encountering similar issues but I now have a working configuration.

config.json includes the following:

          "oidc_providers": {
                "azure": {
                        "color": "blue",
                        "display_name": "Sign in with Azure (Entra ID)",
                        "provider_url": "https://login.microsoftonline.com/<Tennant ID>/v2.0",
                        "client_id": "<ID>",
                        "client_secret": "<secret>",
                        "redirect_url": "https://semaphore.test.com/api/auth/oidc/azure/redirect"
                }
        },

Note of course that the redirect_uri should match what is set in AzureAD as a valid (Web) redirect uri: https://semaphore.test.com/api/auth/oidc/azure/redirect

My instance is behind an Nginx reverse proxy, but that config is pretty vanilla like in this documentation here: https://docs.ansible-semaphore.com/administration-guide/security

But for the record, my Nginx configuration for this site is is like:

upstream semaphore {
    server 127.0.0.1:3000;
  }

server {
    listen 80;
    server_name semaphore.test.com;
    return 301 https://semaphore.test.com$request_uri;
}

server {
        listen 443 ssl;
        ssl_certificate /etc/ssl/server.test.semaphore.pem;
        ssl_certificate_key /etc/ssl/server.test.semaphore.pem;
        server_name semaphore.test.com;
        client_max_body_size 0;
        chunked_transfer_encoding on;

    location / {
      proxy_pass http://semaphore/;
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

      proxy_set_header X-Forwarded-Proto $scheme;

      proxy_buffering off;
      proxy_request_buffering off;
    }

    location /api/ws {
      proxy_pass http://semaphore/api/ws;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      proxy_set_header Origin "";
    }
}
RyanGelinas commented 5 months ago

This example worked for me, so I created a PR in the docs repo to add it there. Thanks @xfact-joseph-p !

tboerger commented 5 months ago

There is also a config including all scopes at https://github.com/semaphoreui/semaphore/discussions/2031#discussioncomment-9536050 which should be added to the docs.

m-soltani commented 5 months ago

I am having the same issue and have configured my oidc provider as in #2031. I get the following error:


time="2024-06-29T17:42:32Z" level=error msg="oauth2: \"invalid_grant\" \"AADSTS54005: OAuth2 Authorization code was already redeemed, please retry with a new valid code or use an existing refresh token. Trace ID: 9156e6e8-c9eb-4829-a926-b0d0d6c69a00 Correlation ID: f2714ddd-d611-4023-8044-32499e98c6fb Timestamp: 2024-06-29 17:42:32Z\""

Is it really the case that semaphore server uses duplicate authorization codes?

I am running the docker-compose sample setup from the repository.

m-soltani commented 5 months ago

I am having the same issue and have configured my oidc provider as in #2031. I get the following error:


time="2024-06-29T17:42:32Z" level=error msg="oauth2: \"invalid_grant\" \"AADSTS54005: OAuth2 Authorization code was already redeemed, please retry with a new valid code or use an existing refresh token. Trace ID: 9156e6e8-c9eb-4829-a926-b0d0d6c69a00 Correlation ID: f2714ddd-d611-4023-8044-32499e98c6fb Timestamp: 2024-06-29 17:42:32Z\""

Is it really the case that semaphore server uses duplicate authorization codes?

I am running the docker-compose sample setup from the repository.

It turns out , you need cookie_hash and cookie_encryption properties to be set in order for authentication flow to work correctly. This is not a documented behavior and it was not clear how to generate those keys. I did find a sample in another GitHub issue and use that as baseline. If I could contribute to the documentation, that would be really good - Is this allowed?

fiftin commented 4 months ago

Hi @m-soltani https://docs.semaphoreui.com/administration-guide/security