diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e423df4..4d811c8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,8 @@ ## Changes since v5.1.1 - [#615](https://github.com/oauth2-proxy/oauth2-proxy/pull/615) Helm Example based on Kind cluster and Nginx ingress (@EvgeniGordeev) +- [#604](https://github.com/oauth2-proxy/oauth2-proxy/pull/604) Add Keycloak local testing environment (@EvgeniGordeev) +- [#539](https://github.com/oauth2-proxy/oauth2-proxy/pull/539) Refactor encryption ciphers and add AES-GCM support (@NickMeves) - [#601](https://github.com/oauth2-proxy/oauth2-proxy/pull/601) Ensure decrypted user/email are valid UTF8 (@JoelSpeed) - [#560](https://github.com/oauth2-proxy/oauth2-proxy/pull/560) Fallback to UserInfo is User ID claim not present (@JoelSpeed) - [#598](https://github.com/oauth2-proxy/oauth2-proxy/pull/598) acr_values no longer sent to IdP when empty (@ScottGuymer) diff --git a/contrib/local-environment/Makefile b/contrib/local-environment/Makefile index 0cfeaa66..f3df6d33 100644 --- a/contrib/local-environment/Makefile +++ b/contrib/local-environment/Makefile @@ -13,3 +13,11 @@ nginx-up: .PHONY: nginx-% nginx-%: docker-compose -f docker-compose.yaml -f docker-compose-nginx.yaml $* + +.PHONY: keycloak-up +keycloak-up: + docker-compose -f docker-compose-keycloak.yaml up -d + +.PHONY: keycloak-% +keycloak-%: + docker-compose -f docker-compose-keycloak.yaml $* diff --git a/contrib/local-environment/docker-compose-keycloak.yaml b/contrib/local-environment/docker-compose-keycloak.yaml new file mode 100644 index 00000000..f78ce0fa --- /dev/null +++ b/contrib/local-environment/docker-compose-keycloak.yaml @@ -0,0 +1,70 @@ +# This docker-compose file can be used to bring up an example instance of oauth2-proxy +# for manual testing and exploration of features. +# Alongside OAuth2-Proxy, this file also starts Keycloak to act as the identity provider, +# HTTPBin as an example upstream. +# +# This can either be created using docker-compose +# docker-compose -f docker-compose-keycloak.yaml +# Or: +# make keycloak- (eg. make keycloak-up, make keycloak-down) +# +# Access http://oauth2-proxy.localtest.me:4180 to initiate a login cycle using user=admin@example.com, password=password +# Access http://keycloak.localtest.me:9080 with the same credentials to check out the settings +version: '3.0' +services: + + oauth2-proxy: + container_name: oauth2-proxy + image: quay.io/oauth2-proxy/oauth2-proxy:v5.1.1 + command: --config /oauth2-proxy.cfg + hostname: oauth2-proxy + volumes: + - "./oauth2-proxy-keycloak.cfg:/oauth2-proxy.cfg" + restart: unless-stopped + networks: + keycloak: {} + httpbin: {} + oauth2-proxy: {} + depends_on: + - httpbin + - keycloak + ports: + - 4180:4180/tcp + + httpbin: + container_name: httpbin + image: kennethreitz/httpbin:latest + hostname: httpbin + networks: + httpbin: {} + + keycloak: + container_name: keycloak + image: jboss/keycloak:10.0.0 + hostname: keycloak + command: + [ + '-b', + '0.0.0.0', + '-Djboss.socket.binding.port-offset=1000', + '-Dkeycloak.migration.action=import', + '-Dkeycloak.migration.provider=dir', + '-Dkeycloak.migration.dir=/realm-config', + '-Dkeycloak.migration.strategy=IGNORE_EXISTING', + ] + volumes: + - ./keycloak:/realm-config + environment: + KEYCLOAK_USER: admin@example.com + KEYCLOAK_PASSWORD: password + networks: + keycloak: + aliases: + - keycloak.localtest.me + ports: + - 9080:9080/tcp + +networks: + httpbin: {} + keycloak: {} + oauth2-proxy: {} diff --git a/contrib/local-environment/keycloak/master-realm.json b/contrib/local-environment/keycloak/master-realm.json new file mode 100644 index 00000000..3b9ae7dc --- /dev/null +++ b/contrib/local-environment/keycloak/master-realm.json @@ -0,0 +1,1684 @@ +{ + "id" : "master", + "realm" : "master", + "displayName" : "Keycloak", + "displayNameHtml" : "
Keycloak
", + "notBefore" : 0, + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 60, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "32626c92-4327-40f1-b318-76a6b5c7eee5", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "master", + "attributes" : { } + }, { + "id" : "e36da570-7ae0-4323-8b39-73eb92ce722f", + "name" : "admin", + "description" : "${role_admin}", + "composite" : true, + "composites" : { + "realm" : [ "create-realm" ], + "client" : { + "master-realm" : [ "query-groups", "create-client", "query-realms", "view-authorization", "view-realm", "manage-clients", "query-users", "manage-realm", "view-events", "manage-events", "view-identity-providers", "view-users", "manage-identity-providers", "manage-authorization", "manage-users", "view-clients", "query-clients", "impersonation" ] + } + }, + "clientRole" : false, + "containerId" : "master", + "attributes" : { } + }, { + "id" : "71aca46c-6fcf-4456-ba87-6374e70108a2", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "master", + "attributes" : { } + }, { + "id" : "6ca3fee8-1a3f-4068-a311-6e81223a884b", + "name" : "create-realm", + "description" : "${role_create-realm}", + "composite" : false, + "clientRole" : false, + "containerId" : "master", + "attributes" : { } + } ], + "client" : { + "oauth2-proxy" : [ ], + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "2cc5e40c-0a28-4c09-85eb-20cd47ac1351", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "380985f1-61c7-4940-93ae-7a09458071ca", + "attributes" : { } + } ], + "master-realm" : [ { + "id" : "a8271c2c-6437-4ca5-ae83-49ea5fe1318d", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "5a7cb1ae-7dac-486b-bf7b-4d7fbc5adb31", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "a9e6a2fa-c31b-4959-bf8a-a46fcc9c65ec", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "1cef34e3-569a-4d2b-ba5c-aafe5c7ab423", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "efc46075-30cd-4600-aa92-2ae4a171d0c2", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "9ffacaf0-afc6-49e9-8708-ef35ac40f3f8", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "90662091-b3bc-4ae4-83c9-a4f53e7e9eeb", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "9a5fbc9d-6fae-4155-86f6-72fd399aa126", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "03f46127-9436-477d-8c7f-58569f45237c", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "f10eaea2-90ab-4310-9d5f-8d986564d061", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "2403e038-2cf7-4b06-b5cb-33a417a00d8d", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "677d057b-66f8-4163-9948-95fdbd06dfdc", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-groups", "query-users" ] + } + }, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "dc140fa6-bf2c-49f2-b8c9-fc34ef8a2c63", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "155bf234-4895-4855-95c2-a460518f57e8", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "5441ec71-3eac-4696-9e68-0de54fbdde98", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "2db0f052-cb91-4170-81fd-107756b162f7", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "e1d7f235-8bf2-40b8-be49-49aca70a5088", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + }, { + "id" : "e743f66a-2f56-4b97-b34b-33f06ff1e739", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", + "attributes" : { } + } ], + "account" : [ { + "id" : "64d8f532-839e-4386-b2eb-fe8848b0a9de", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", + "attributes" : { } + }, { + "id" : "3ec22748-960f-4f96-a43e-50f54a02dc23", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", + "attributes" : { } + }, { + "id" : "177d18e4-46b0-4ea3-8b70-327486ce5bb2", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", + "attributes" : { } + }, { + "id" : "703643d6-0542-4e27-9737-7c442925c18c", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", + "attributes" : { } + }, { + "id" : "c64f9f66-d762-4337-8833-cf31c316e8a7", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", + "attributes" : { } + }, { + "id" : "611f568b-0fdd-4d2e-ba34-03136cd486c4", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", + "attributes" : { } + } ] + } + }, + "groups" : [ ], + "defaultRoles" : [ "offline_access", "uma_authorization" ], + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpSupportedApplications" : [ "FreeOTP", "Google Authenticator" ], + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account" ] + } ] + }, + "clients" : [ { + "id" : "a367038f-fe01-4459-9f91-7ad0cf498533", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/master/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "0896a464-da81-4454-bee9-b56bdbad9e7f", + "defaultRoles" : [ "view-profile", "manage-account" ], + "redirectUris" : [ "/realms/master/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "72f75604-1e21-407c-b967-790aafd11534", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/master/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "91f85142-ee18-4e30-9949-e5acb701bdee", + "redirectUris" : [ "/realms/master/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "2772c101-0dba-49b7-9627-5aaddc666939", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "b13fd0de-3be0-4a08-bc5d-d1de34421b1a", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "4640af2e-b4a6-44eb-85ec-6278a62a4f01", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "380985f1-61c7-4940-93ae-7a09458071ca", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "65d2ba2b-bcae-49ff-9f56-77c818f55930", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "7174c175-1887-4e57-b95b-969fe040deff", + "clientId" : "master-realm", + "name" : "master Realm", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "40f73851-a94c-4091-90de-aeee8ca1acf8", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, + { + "id": "0493c7c6-6e20-49ea-9acb-627c0b52d400", + "clientId": "oauth2-proxy", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "72341b6d-7065-4518-a0e4-50ee15025608", + "redirectUris": [ + "http://oauth2-proxy.localtest.me:4180/oauth2/callback" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "role_list", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, { + "id" : "2a3ad1fd-a30d-4b72-89c4-bed12f178338", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/master/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "b234b7aa-8417-410f-b3fd-c57434d3aa4a", + "redirectUris" : [ "/admin/master/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "5885b0d3-a917-4b52-8380-f37d0754a2ef", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "47ea3b67-4f0c-4c7e-8ac6-a33a3d655894", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${addressScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "4be0ca19-0ec7-4cc1-b263-845ea539ff12", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "aba72e57-540f-4825-95b7-2d143be028cc", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${emailScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "7fe82724-5748-4b6d-9708-a028f5d3b970", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "e42f334e-cfae-44a0-905d-c3ef215feaae", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "ec765598-bd71-4318-86c3-b3f81a41c99e", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "90694036-4014-4672-a2c8-c68319e9308a", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + }, { + "id" : "f7b0fcc0-6139-4158-ac45-34fd9a58a5ef", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "multivalued" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "8a09267b-3634-4a9c-baab-6f2fb4137347", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "3a48c5dd-33a8-4be0-9d2e-30fd7f98363a", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${phoneScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "5427d1b4-ba79-412a-b23c-da640a98980c", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + }, { + "id" : "31d4a53f-6503-40e8-bd9d-79a7c46c4fbe", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "5921a9e9-7fec-4471-95e3-dd96eebdec58", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${profileScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "4fa92092-ee0d-4dc7-a63b-1e3b02d35ebb", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "1a5cc2e2-c983-4150-8583-23a7f5c826bf", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "67931f77-722a-492d-b581-a953e26b7d44", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "10f6ac36-3a63-4e1c-ac69-c095588f5967", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "205d9dce-b6c8-4b1d-9c9c-fa24788651cf", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "638216c8-ea8c-40e3-9429-771e9278920e", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "39c17eae-8ea7-422c-ae21-b8876bf12184", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "01c559cf-94f2-46ad-b965-3b2e1db1a2a6", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "String" + } + }, { + "id" : "1693b5ab-28eb-485d-835d-2ae070ccb3ba", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "a0e08332-954c-46d2-9795-56eb31132580", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "cea0cd9c-d085-4d19-acc3-4bb41c891b68", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "3122097d-4cba-46c2-8b3b-5d87a4cc605e", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "a3b97897-d913-4e0a-a4cf-033ce78f7d24", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "a44eeb9d-410d-49c5-b0e0-5d84787627ad", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "651408a7-6704-4198-a60f-988821b633ea", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "a8c56c7b-ccbc-4b01-8df5-3ecb6328755f", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "13ec0fd3-e64a-4d6f-9be7-c8760f2c9d6b", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${rolesScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "75e741f8-dcd5-49d2-815e-8604ec1d08a1", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "06a2d506-4996-4a33-8c43-2cf64af6a630", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "3c3470df-d414-4e1c-87fc-3fb3cea34b8d", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "d85aba25-c74b-49e3-9ccb-77b4bb16efa5", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "86b3f64f-1525-4500-bcbc-9b889b25f995", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + } ], + "defaultDefaultClientScopes" : [ "roles", "profile", "role_list", "email", "web-origins" ], + "defaultOptionalClientScopes" : [ "phone", "address", "offline_access", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "xXSSProtection" : "1; mode=block", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "59048b39-ad0f-4d12-8c52-7cfc2c43278a", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "saml-role-list-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper" ] + } + }, { + "id" : "760559a6-a59f-4175-9ac5-6f3612e20129", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "24f4cb42-76bd-499e-812a-4e0d270c9e13", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "abbfc599-480a-44ef-8e33-73a83eaab166", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper" ] + } + }, { + "id" : "3c6450f0-4521-402b-a247-c8165854b1fa", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "d9b64399-744b-498e-9d35-f68b1582bd7d", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "22f15f1f-3116-4348-a1e5-fc0d7576452a", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "4ad7b291-ddbb-4674-8c3d-ab8fd76d4168", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "f71cc325-9907-4d27-a0e6-88fca7450e5e", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "6c7d982e-372f-49c6-a4f3-5c451fb85eca" ], + "secret" : [ "yH6M3W7aOgh2_cKJ0srWbw" ], + "priority" : [ "100" ] + } + }, { + "id" : "7b50d0ab-dda5-4624-aa42-b4b397724ce1", + "name" : "hmac-generated", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "587f0fb5-845d-4b45-87a0-84145092aaef" ], + "secret" : [ "PuH8Lxh9GeNfGJRDk34SWIlBDdrJpC3U3SfcxqqQtlIf2DBzRKUu8VbDVrmMN5b5CoPsJhrQ2SVb-iE9Lzsb3A" ], + "priority" : [ "100" ], + "algorithm" : [ "HS256" ] + } + }, { + "id" : "547c1c71-9f97-4e12-801b-ed5c2cc61bba", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEAjdo2HZ5ruNnIbkSeAfFYpbPvJw3vtz/VuKJerC4mUXYd7qRMhs3VLJZ3mFyeCuO8W81vkGrFiC9KQnX2lHj2dtA/RWEJw5bpz+JdOFr5pvXg0lQ0sa+hro9afWDygTU4FmLsEi5z98847TbH178RT6n7+JVqZ9jYU9rSpwVTC8E/4yxSuStmhGCcAkZ6dGhHNBdvGUgwxKYj7dYLRJiI+nilIdKuxPzxI/YZxZnXBHDdbNXJgDymTQPut99OnBxeZbH38CJ1MNo3VdV1fzOMGUHe+vn/EOD5E+pXC8PwvJnWU+XHUTFVZeyIXehh3pYLUsq/6bZ1MYsEaFIhznOkwwIDAQABAoIBAHB+64fVyUxRurhoRn737fuLlU/9p2xGfbHtYvNdrhnQeLB3MBGAT10K/1Gfsd6k+Q49AAsiAgGcr2HBt4nL3HohcOwOpvWsS0UIGjHFRFP6jw9+pEN+K9UJ7xObvPZnRFHMpbdNi76tYlINrbMV3h61ihR8OmSc/gKSeZjnihK5OkaNnlqGRaBM/koI+iAxUHuJPnBLBZmD4T8eIfE4S2TvUeVeQogI9Muvnb9tIPJ5XyP9iXWLdRjnek/+wTdxHHZuo06Tc0bMjRaTHiF6K9ntOM2EmQb6bS2J47zgzRLNFE22BWH7RJq659EzElkOn0C0k7dWDTur/3Lpx1+zxJECgYEA8t+J3J+9oGTFmY2VPH05Yr/R/iVDOyIOlO1CmgonOQ3KPhbfNBB3aTAJP20LOZChN4JoWuiZJg4UwzXOeY9DvdDkPO0YLlSjPAIwJNk+xcxFcp5hqMUul2db+cgEY8zp0Wg9kFOq3JmJjK4+1+fgsVnOB+B08ZYI6bZzsUVKzucCgYEAlYTrsxs6fQua0cvZNQPYNZzwF3LVwPBl/ntkdRBE3HGyZeCAhIj7e9pAUusCPsQJaCmW2UEmywD/aIxGrBkojzTKItshM3PN1PYKL8W0Zq+H67uF5KfdvsbabZWHfP/LGCpoKF8Ov7JVPPqGrZ03Z2SheeLZAtNeHN4OB1u9i8UCgYATkS7qN3Rvl67T0DRVy0D0U7/3Wckw2m2SUgsrneXLEvFYTz9sUmdMcjJMidx9pslWT4tYx6SPDFNf5tXbtU8f29SHlBJ+qRL9oq9+SIJmLS7rLRdxIXG/gPRIC3VPFRNBa8SJ/DOn0jbivqcRffz8TN/sgojpbc0KB0kK3ypHwQKBgCKVCcb1R0PgyUA4+9YNO5a647UotFPZxl1jwMpqpuKt0WtKz67X2AK/ah1DidNmmB5lcCRzsztE0c4mk7n+X6kvtoj1UeqKoFLfTV/bRGxzsOZPCxrl0J3tdFvgN+QrbZf7Rvf/dHPWFWzzLO8+66+YUNjWJQdIR/45Rdlh2KdZAoGBAMfF3ir+fe3KdQ6hAf9QyrLxJ5l+GO+IgtxXGbon7eeJBIZHHdMeDy4pC7DMcI214BmIntbyY+xS+gI3oM26EJUVmrZ6tkyIDFsCHm9rcXG9ogvffzQWM1Wqzm27hR/3s+EPWW9AOcIimiFV1UPp/mLjnrCuq58V2aJS/TT14oLe" ], + "certificate" : [ "MIICmzCCAYMCBgFygL/j4DANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjAwNjA0MTkxMDU4WhcNMzAwNjA0MTkxMjM4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN2jYdnmu42chuRJ4B8Vils+8nDe+3P9W4ol6sLiZRdh3upEyGzdUslneYXJ4K47xbzW+QasWIL0pCdfaUePZ20D9FYQnDlunP4l04Wvmm9eDSVDSxr6Guj1p9YPKBNTgWYuwSLnP3zzjtNsfXvxFPqfv4lWpn2NhT2tKnBVMLwT/jLFK5K2aEYJwCRnp0aEc0F28ZSDDEpiPt1gtEmIj6eKUh0q7E/PEj9hnFmdcEcN1s1cmAPKZNA+63306cHF5lsffwInUw2jdV1XV/M4wZQd76+f8Q4PkT6lcLw/C8mdZT5cdRMVVl7Ihd6GHelgtSyr/ptnUxiwRoUiHOc6TDAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIAqydMYxa51kNEyfXyR2kStlglE4LDeLBLHDABeBPE0eN2awoH/mw3kXS4OA/C0e3c7bAwViOzOVERGeUNiBvP5rL1Amuu97nwFcxhkTaJH4ZwCGkxceaIo9LNDpAEesqHLQSdplFXIA4TbEFoKMem4k31KVU7i9/rUesrSRmxLptIOK7LLvRMYiY/t7tdAvoZAtoliuQlFKQywEuxXQrCkcoVEAARABWGt0rsWC2xK0tVxHRIrENwvMp/aUYd17sZ0403aaS9dlvfQ63ExnaHd+++RJtPku8P220Tw27YVmFAwzJgS0aUpEaDsgRNz6OMSyxEg/n7eKK08aU3szwQ=" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "3253f9b7-905d-4458-ad8a-8ada5e16d195", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "ALTERNATIVE", + "priority" : 20, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "75bd854e-ab99-46f1-90ed-a8bfc1559558", + "alias" : "Authentication Options", + "description" : "Authentication options.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "basic-auth", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "basic-auth-otp", + "requirement" : "DISABLED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-spnego", + "requirement" : "DISABLED", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "9b0e6cce-62c5-4fb6-a48d-e07c950e38c3", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-otp-form", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "1c26fd14-ac06-4dc1-bdd8-8c34c1b41720", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "254f7549-51ec-4565-a736-35c07b6e25f0", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-otp-form", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "b2413da8-3de9-4bfe-b77e-643fd1964c8f", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "REQUIRED", + "priority" : 20, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "f8392bfb-8dce-4a16-8af1-b2a4d1a0a273", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-otp", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "fb69c297-b26e-44fa-aabd-d7b40eec3cd3", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "ALTERNATIVE", + "priority" : 20, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "de3a41a9-7018-4931-9c4d-d04f9501b2ce", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "CONDITIONAL", + "priority" : 20, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "6526b0d1-b48e-46c6-bb08-11ebcf458def", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "auth-spnego", + "requirement" : "DISABLED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "identity-provider-redirector", + "requirement" : "ALTERNATIVE", + "priority" : 25, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "ALTERNATIVE", + "priority" : 30, + "flowAlias" : "forms", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "92a653ba-8f2d-4283-8354-ca55f9d89181", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "client-jwt", + "requirement" : "ALTERNATIVE", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "client-secret-jwt", + "requirement" : "ALTERNATIVE", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "client-x509", + "requirement" : "ALTERNATIVE", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "e365be39-78db-46f0-b2e8-4e7001c2f5d0", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "direct-grant-validate-password", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "CONDITIONAL", + "priority" : 30, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "dd61caf5-a40f-48b7-9e8c-a1f3b67041dd", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "7a055643-62e1-4ac1-b126-9a8d6c299635", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "REQUIRED", + "priority" : 20, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "fe8bc7ee-6e8f-436e-8336-c60fcd350843", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "CONDITIONAL", + "priority" : 20, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "3646f08e-ab70-415b-a701-6ed2e2d214c9", + "alias" : "http challenge", + "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "no-cookie-redirect", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "REQUIRED", + "priority" : 20, + "flowAlias" : "Authentication Options", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "04176530-0972-47ad-83df-19d8534caac2", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "requirement" : "REQUIRED", + "priority" : 10, + "flowAlias" : "registration form", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "fa0ed569-6746-439e-b07e-89f7ed918c07", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-profile-action", + "requirement" : "REQUIRED", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-password-action", + "requirement" : "REQUIRED", + "priority" : 50, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-recaptcha-action", + "requirement" : "DISABLED", + "priority" : 60, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "03680917-28f3-4ccd-bdf6-4a516f7c0018", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-credential-email", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-password", + "requirement" : "REQUIRED", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "CONDITIONAL", + "priority" : 40, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "19a9d9aa-2d2b-4701-807f-c384ab921c7e", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "534f01f4-45b3-43a0-91d1-238860cc126d", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "65bb9337-9633-4a21-8f6f-1d4129f664ac", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "terms_and_conditions", + "name" : "Terms and Conditions", + "providerId" : "terms_and_conditions", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "attributes" : { }, + "keycloakVersion" : "10.0.0", + "userManagedAccessAllowed" : false +} diff --git a/contrib/local-environment/keycloak/master-users-0.json b/contrib/local-environment/keycloak/master-users-0.json new file mode 100644 index 00000000..54d66160 --- /dev/null +++ b/contrib/local-environment/keycloak/master-users-0.json @@ -0,0 +1,27 @@ +{ + "realm" : "master", + "users" : [ { + "id" : "3356c0a0-d4d5-4436-9c5a-2299c71c08ec", + "createdTimestamp" : 1591297959169, + "username" : "admin@example.com", + "email" : "admin@example.com", + "enabled" : true, + "totp" : false, + "emailVerified" : true, + "credentials" : [ { + "id" : "a1a06ecd-fdc0-4e67-92cd-2da22d724e32", + "type" : "password", + "createdDate" : 1591297959315, + "secretData" : "{\"value\":\"6rt5zuqHVHopvd0FTFE0CYadXTtzY0mDY2BrqnNQGS51/7DfMJeGgj0roNnGMGvDv30imErNmiSOYl+cL9jiIA==\",\"salt\":\"LI0kqr09JB7J9wvr2Hxzzg==\"}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\"}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "offline_access", "admin", "uma_authorization" ], + "clientRoles" : { + "account" : [ "view-profile", "manage-account" ] + }, + "notBefore" : 0, + "groups" : [ ] + } ] +} diff --git a/contrib/local-environment/oauth2-proxy-keycloak.cfg b/contrib/local-environment/oauth2-proxy-keycloak.cfg new file mode 100644 index 00000000..6620b8ad --- /dev/null +++ b/contrib/local-environment/oauth2-proxy-keycloak.cfg @@ -0,0 +1,20 @@ +http_address="0.0.0.0:4180" +cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w=" +email_domains=["example.com"] +cookie_secure="false" +upstreams="http://httpbin" +cookie_domains=[".localtest.me"] # Required so cookie can be read on all subdomains. +whitelist_domains=[".localtest.me"] # Required to allow redirection back to original requested target. + +# keycloak provider +client_secret="72341b6d-7065-4518-a0e4-50ee15025608" +client_id="oauth2-proxy" +redirect_url="http://oauth2-proxy.localtest.me:4180/oauth2/callback" + +# in this case oauth2-proxy is going to visit +# http://keycloak.localtest.me:9080/auth/realms/master/.well-known/openid-configuration for configuration +oidc_issuer_url="http://keycloak.localtest.me:9080/auth/realms/master" +provider="oidc" +provider_display_name="Keycloak" + + diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index d4e6fc5b..dfe1e85f 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -44,7 +44,7 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example | `--cookie-samesite` | string | set SameSite cookie attribute (ie: `"lax"`, `"strict"`, `"none"`, or `""`). | `""` | | `--custom-templates-dir` | string | path to custom html templates | | | `--display-htpasswd-form` | bool | display username / password login form if an htpasswd file is provided | true | -| `--email-domain` | string | authenticate emails with the specified domain (may be given multiple times). Use `*` to authenticate any email | | +| `--email-domain` | string \| list | authenticate emails with the specified domain (may be given multiple times). Use `*` to authenticate any email | | | `--extra-jwt-issuers` | string | if `--skip-jwt-bearer-tokens` is set, a list of extra JWT `issuer=audience` pairs (where the issuer URL has a `.well-known/openid-configuration` or a `.well-known/jwks.json`) | | | `--exclude-logging-paths` | string | comma separated list of paths to exclude from logging, eg: `"/ping,/path2"` |`""` (no paths excluded) | | `--flush-interval` | duration | period between flushing response buffers when streaming responses | `"1s"` | diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index b490baf7..3b2a4d19 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -4,9 +4,9 @@ import "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" // SessionOptions contains configuration options for the SessionStore providers. type SessionOptions struct { - Type string `flag:"session-store-type" cfg:"session_store_type"` - Cipher *encryption.Cipher `cfg:",internal"` - Redis RedisStoreOptions `cfg:",squash"` + Type string `flag:"session-store-type" cfg:"session_store_type"` + Cipher encryption.Cipher `cfg:",internal"` + Redis RedisStoreOptions `cfg:",squash"` } // CookieSessionStoreType is used to indicate the CookieSessionStore should be diff --git a/pkg/apis/sessions/session_state.go b/pkg/apis/sessions/session_state.go index f2e6633e..44b91bd2 100644 --- a/pkg/apis/sessions/session_state.go +++ b/pkg/apis/sessions/session_state.go @@ -60,7 +60,7 @@ func (s *SessionState) String() string { } // EncodeSessionState returns string representation of the current session -func (s *SessionState) EncodeSessionState(c *encryption.Cipher) (string, error) { +func (s *SessionState) EncodeSessionState(c encryption.Cipher) (string, error) { var ss SessionState if c == nil { // Store only Email and User when cipher is unavailable @@ -77,7 +77,7 @@ func (s *SessionState) EncodeSessionState(c *encryption.Cipher) (string, error) &ss.IDToken, &ss.RefreshToken, } { - err := c.EncryptInto(s) + err := into(s, c.Encrypt) if err != nil { return "", err } @@ -89,7 +89,7 @@ func (s *SessionState) EncodeSessionState(c *encryption.Cipher) (string, error) } // DecodeSessionState decodes the session cookie string into a SessionState -func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) { +func DecodeSessionState(v string, c encryption.Cipher) (*SessionState, error) { var ss SessionState err := json.Unmarshal([]byte(v), &ss) if err != nil { @@ -104,24 +104,18 @@ func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) { PreferredUsername: ss.PreferredUsername, } } else { - // Backward compatibility with using unencrypted Email - if ss.Email != "" { - decryptedEmail, errEmail := c.Decrypt(ss.Email) - if errEmail == nil { - if !utf8.ValidString(decryptedEmail) { - return nil, errors.New("invalid value for decrypted email") - } - ss.Email = decryptedEmail + // Backward compatibility with using unencrypted Email or User + // Decryption errors will leave original string + err = into(&ss.Email, c.Decrypt) + if err == nil { + if !utf8.ValidString(ss.Email) { + return nil, errors.New("invalid value for decrypted email") } } - // Backward compatibility with using unencrypted User - if ss.User != "" { - decryptedUser, errUser := c.Decrypt(ss.User) - if errUser == nil { - if !utf8.ValidString(decryptedUser) { - return nil, errors.New("invalid value for decrypted user") - } - ss.User = decryptedUser + err = into(&ss.User, c.Decrypt) + if err == nil { + if !utf8.ValidString(ss.User) { + return nil, errors.New("invalid value for decrypted user") } } @@ -131,7 +125,7 @@ func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) { &ss.IDToken, &ss.RefreshToken, } { - err := c.DecryptInto(s) + err := into(s, c.Decrypt) if err != nil { return nil, err } @@ -139,3 +133,20 @@ func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) { } return &ss, nil } + +// codecFunc is a function that takes a []byte and encodes/decodes it +type codecFunc func([]byte) ([]byte, error) + +func into(s *string, f codecFunc) error { + // Do not encrypt/decrypt nil or empty strings + if s == nil || *s == "" { + return nil + } + + d, err := f([]byte(*s)) + if err != nil { + return err + } + *s = string(d) + return nil +} diff --git a/pkg/apis/sessions/session_state_test.go b/pkg/apis/sessions/session_state_test.go index 150e9c9d..3e9554c5 100644 --- a/pkg/apis/sessions/session_state_test.go +++ b/pkg/apis/sessions/session_state_test.go @@ -1,11 +1,13 @@ -package sessions_test +package sessions import ( + "crypto/rand" "fmt" + "io" + mathrand "math/rand" "testing" "time" - "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" "github.com/stretchr/testify/assert" ) @@ -17,12 +19,16 @@ func timePtr(t time.Time) *time.Time { return &t } +func newTestCipher(secret []byte) (encryption.Cipher, error) { + return encryption.NewBase64Cipher(encryption.NewCFBCipher, secret) +} + func TestSessionStateSerialization(t *testing.T) { - c, err := encryption.NewCipher([]byte(secret)) + c, err := newTestCipher([]byte(secret)) assert.Equal(t, nil, err) - c2, err := encryption.NewCipher([]byte(altSecret)) + c2, err := newTestCipher([]byte(altSecret)) assert.Equal(t, nil, err) - s := &sessions.SessionState{ + s := &SessionState{ Email: "user@domain.com", PreferredUsername: "user", AccessToken: "token1234", @@ -34,7 +40,7 @@ func TestSessionStateSerialization(t *testing.T) { encoded, err := s.EncodeSessionState(c) assert.Equal(t, nil, err) - ss, err := sessions.DecodeSessionState(encoded, c) + ss, err := DecodeSessionState(encoded, c) t.Logf("%#v", ss) assert.Equal(t, nil, err) assert.Equal(t, "", ss.User) @@ -47,17 +53,17 @@ func TestSessionStateSerialization(t *testing.T) { assert.Equal(t, s.RefreshToken, ss.RefreshToken) // ensure a different cipher can't decode properly (ie: it gets gibberish) - ss, err = sessions.DecodeSessionState(encoded, c2) + ss, err = DecodeSessionState(encoded, c2) t.Logf("%#v", ss) assert.NotEqual(t, nil, err) } func TestSessionStateSerializationWithUser(t *testing.T) { - c, err := encryption.NewCipher([]byte(secret)) + c, err := newTestCipher([]byte(secret)) assert.Equal(t, nil, err) - c2, err := encryption.NewCipher([]byte(altSecret)) + c2, err := newTestCipher([]byte(altSecret)) assert.Equal(t, nil, err) - s := &sessions.SessionState{ + s := &SessionState{ User: "just-user", PreferredUsername: "ju", Email: "user@domain.com", @@ -69,7 +75,7 @@ func TestSessionStateSerializationWithUser(t *testing.T) { encoded, err := s.EncodeSessionState(c) assert.Equal(t, nil, err) - ss, err := sessions.DecodeSessionState(encoded, c) + ss, err := DecodeSessionState(encoded, c) t.Logf("%#v", ss) assert.Equal(t, nil, err) assert.Equal(t, s.User, ss.User) @@ -81,13 +87,13 @@ func TestSessionStateSerializationWithUser(t *testing.T) { assert.Equal(t, s.RefreshToken, ss.RefreshToken) // ensure a different cipher can't decode properly (ie: it gets gibberish) - ss, err = sessions.DecodeSessionState(encoded, c2) + ss, err = DecodeSessionState(encoded, c2) t.Logf("%#v", ss) assert.NotEqual(t, nil, err) } func TestSessionStateSerializationNoCipher(t *testing.T) { - s := &sessions.SessionState{ + s := &SessionState{ Email: "user@domain.com", PreferredUsername: "user", AccessToken: "token1234", @@ -99,7 +105,7 @@ func TestSessionStateSerializationNoCipher(t *testing.T) { assert.Equal(t, nil, err) // only email should have been serialized - ss, err := sessions.DecodeSessionState(encoded, nil) + ss, err := DecodeSessionState(encoded, nil) assert.Equal(t, nil, err) assert.Equal(t, "", ss.User) assert.Equal(t, s.Email, ss.Email) @@ -109,7 +115,7 @@ func TestSessionStateSerializationNoCipher(t *testing.T) { } func TestSessionStateSerializationNoCipherWithUser(t *testing.T) { - s := &sessions.SessionState{ + s := &SessionState{ User: "just-user", Email: "user@domain.com", PreferredUsername: "user", @@ -122,7 +128,7 @@ func TestSessionStateSerializationNoCipherWithUser(t *testing.T) { assert.Equal(t, nil, err) // only email should have been serialized - ss, err := sessions.DecodeSessionState(encoded, nil) + ss, err := DecodeSessionState(encoded, nil) assert.Equal(t, nil, err) assert.Equal(t, s.User, ss.User) assert.Equal(t, s.Email, ss.Email) @@ -132,20 +138,20 @@ func TestSessionStateSerializationNoCipherWithUser(t *testing.T) { } func TestExpired(t *testing.T) { - s := &sessions.SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(-1) * time.Minute))} + s := &SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(-1) * time.Minute))} assert.Equal(t, true, s.IsExpired()) - s = &sessions.SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Minute))} + s = &SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Minute))} assert.Equal(t, false, s.IsExpired()) - s = &sessions.SessionState{} + s = &SessionState{} assert.Equal(t, false, s.IsExpired()) } type testCase struct { - sessions.SessionState + SessionState Encoded string - Cipher *encryption.Cipher + Cipher encryption.Cipher Error bool } @@ -159,14 +165,14 @@ func TestEncodeSessionState(t *testing.T) { testCases := []testCase{ { - SessionState: sessions.SessionState{ + SessionState: SessionState{ Email: "user@domain.com", User: "just-user", }, Encoded: `{"Email":"user@domain.com","User":"just-user"}`, }, { - SessionState: sessions.SessionState{ + SessionState: SessionState{ Email: "user@domain.com", User: "just-user", AccessToken: "token1234", @@ -181,7 +187,7 @@ func TestEncodeSessionState(t *testing.T) { for i, tc := range testCases { encoded, err := tc.EncodeSessionState(tc.Cipher) - t.Logf("i:%d Encoded:%#vsessions.SessionState:%#v Error:%#v", i, encoded, tc.SessionState, err) + t.Logf("i:%d Encoded:%#vSessionState:%#v Error:%#v", i, encoded, tc.SessionState, err) if tc.Error { assert.Error(t, err) assert.Empty(t, encoded) @@ -201,39 +207,39 @@ func TestDecodeSessionState(t *testing.T) { eJSON, _ := e.MarshalJSON() eString := string(eJSON) - c, err := encryption.NewCipher([]byte(secret)) + c, err := newTestCipher([]byte(secret)) assert.NoError(t, err) testCases := []testCase{ { - SessionState: sessions.SessionState{ + SessionState: SessionState{ Email: "user@domain.com", User: "just-user", }, Encoded: `{"Email":"user@domain.com","User":"just-user"}`, }, { - SessionState: sessions.SessionState{ + SessionState: SessionState{ Email: "user@domain.com", User: "", }, Encoded: `{"Email":"user@domain.com"}`, }, { - SessionState: sessions.SessionState{ + SessionState: SessionState{ User: "just-user", }, Encoded: `{"User":"just-user"}`, }, { - SessionState: sessions.SessionState{ + SessionState: SessionState{ Email: "user@domain.com", User: "just-user", }, Encoded: fmt.Sprintf(`{"Email":"user@domain.com","User":"just-user","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","CreatedAt":%s,"ExpiresOn":%s}`, createdString, eString), }, { - SessionState: sessions.SessionState{ + SessionState: SessionState{ Email: "user@domain.com", User: "just-user", AccessToken: "token1234", @@ -246,7 +252,7 @@ func TestDecodeSessionState(t *testing.T) { Cipher: c, }, { - SessionState: sessions.SessionState{ + SessionState: SessionState{ Email: "user@domain.com", User: "just-user", }, @@ -264,7 +270,7 @@ func TestDecodeSessionState(t *testing.T) { Error: true, }, { - SessionState: sessions.SessionState{ + SessionState: SessionState{ Email: "user@domain.com", User: "YmFzZTY0LWVuY29kZWQtdXNlcgo=", // Base64 encoding of base64-encoded-user }, @@ -274,8 +280,8 @@ func TestDecodeSessionState(t *testing.T) { } for i, tc := range testCases { - ss, err := sessions.DecodeSessionState(tc.Encoded, tc.Cipher) - t.Logf("i:%d Encoded:%#vsessions.SessionState:%#v Error:%#v", i, tc.Encoded, ss, err) + ss, err := DecodeSessionState(tc.Encoded, tc.Cipher) + t.Logf("i:%d Encoded:%#vSessionState:%#v Error:%#v", i, tc.Encoded, ss, err) if tc.Error { assert.Error(t, err) assert.Nil(t, ss) @@ -297,7 +303,7 @@ func TestDecodeSessionState(t *testing.T) { } func TestSessionStateAge(t *testing.T) { - ss := &sessions.SessionState{} + ss := &SessionState{} // Created at unset so should be 0 assert.Equal(t, time.Duration(0), ss.Age()) @@ -306,3 +312,44 @@ func TestSessionStateAge(t *testing.T) { ss.CreatedAt = timePtr(time.Now().Add(-1 * time.Hour)) assert.Equal(t, time.Hour, ss.Age().Round(time.Minute)) } + +func TestIntoEncryptAndIntoDecrypt(t *testing.T) { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + + // Test all 3 valid AES sizes + for _, secretSize := range []int{16, 24, 32} { + t.Run(fmt.Sprintf("%d", secretSize), func(t *testing.T) { + secret := make([]byte, secretSize) + _, err := io.ReadFull(rand.Reader, secret) + assert.Equal(t, nil, err) + + c, err := newTestCipher(secret) + assert.NoError(t, err) + + // Check no errors with empty or nil strings + empty := "" + assert.Equal(t, nil, into(&empty, c.Encrypt)) + assert.Equal(t, nil, into(&empty, c.Decrypt)) + assert.Equal(t, nil, into(nil, c.Encrypt)) + assert.Equal(t, nil, into(nil, c.Decrypt)) + + // Test various sizes tokens might be + for _, dataSize := range []int{10, 100, 1000, 5000, 10000} { + t.Run(fmt.Sprintf("%d", dataSize), func(t *testing.T) { + b := make([]byte, dataSize) + for i := range b { + b[i] = charset[mathrand.Intn(len(charset))] + } + data := string(b) + originalData := data + + assert.Equal(t, nil, into(&data, c.Encrypt)) + assert.NotEqual(t, originalData, data) + + assert.Equal(t, nil, into(&data, c.Decrypt)) + assert.Equal(t, originalData, data) + }) + } + }) + } +} diff --git a/pkg/encryption/cipher.go b/pkg/encryption/cipher.go index 4eb42b03..c1158b5c 100644 --- a/pkg/encryption/cipher.go +++ b/pkg/encryption/cipher.go @@ -3,183 +3,134 @@ package encryption import ( "crypto/aes" "crypto/cipher" - "crypto/hmac" "crypto/rand" - "crypto/sha1" - "crypto/sha256" "encoding/base64" "fmt" - "hash" "io" - "net/http" - "strconv" - "strings" - "time" ) -// SecretBytes attempts to base64 decode the secret, if that fails it treats the secret as binary -func SecretBytes(secret string) []byte { - b, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(secret, "=")) - if err == nil { - // Only return decoded form if a valid AES length - // Don't want unintentional decoding resulting in invalid lengths confusing a user - // that thought they used a 16, 24, 32 length string - for _, i := range []int{16, 24, 32} { - if len(b) == i { - return b - } - } - } - // If decoding didn't work or resulted in non-AES compliant length, - // assume the raw string was the intended secret - return []byte(secret) +// Cipher provides methods to encrypt and decrypt +type Cipher interface { + Encrypt(value []byte) ([]byte, error) + Decrypt(ciphertext []byte) ([]byte, error) } -// cookies are stored in a 3 part (value + timestamp + signature) to enforce that the values are as originally set. -// additionally, the 'value' is encrypted so it's opaque to the browser - -// Validate ensures a cookie is properly signed -func Validate(cookie *http.Cookie, seed string, expiration time.Duration) (value string, t time.Time, ok bool) { - // value, timestamp, sig - parts := strings.Split(cookie.Value, "|") - if len(parts) != 3 { - return - } - if checkSignature(parts[2], seed, cookie.Name, parts[0], parts[1]) { - ts, err := strconv.Atoi(parts[1]) - if err != nil { - return - } - // The expiration timestamp set when the cookie was created - // isn't sent back by the browser. Hence, we check whether the - // creation timestamp stored in the cookie falls within the - // window defined by (Now()-expiration, Now()]. - t = time.Unix(int64(ts), 0) - if t.After(time.Now().Add(expiration*-1)) && t.Before(time.Now().Add(time.Minute*5)) { - // it's a valid cookie. now get the contents - rawValue, err := base64.URLEncoding.DecodeString(parts[0]) - if err == nil { - value = string(rawValue) - ok = true - return - } - } - } - return +type base64Cipher struct { + Cipher Cipher } -// SignedValue returns a cookie that is signed and can later be checked with Validate -func SignedValue(seed string, key string, value string, now time.Time) string { - encodedValue := base64.URLEncoding.EncodeToString([]byte(value)) - timeStr := fmt.Sprintf("%d", now.Unix()) - sig := cookieSignature(sha256.New, seed, key, encodedValue, timeStr) - cookieVal := fmt.Sprintf("%s|%s|%s", encodedValue, timeStr, sig) - return cookieVal -} - -func cookieSignature(signer func() hash.Hash, args ...string) string { - h := hmac.New(signer, []byte(args[0])) - for _, arg := range args[1:] { - h.Write([]byte(arg)) +// NewBase64Cipher returns a new AES Cipher for encrypting cookie values +// and wrapping them in Base64 -- Supports Legacy encryption scheme +func NewBase64Cipher(initCipher func([]byte) (Cipher, error), secret []byte) (Cipher, error) { + c, err := initCipher(secret) + if err != nil { + return nil, err } - var b []byte - b = h.Sum(b) - return base64.URLEncoding.EncodeToString(b) + return &base64Cipher{Cipher: c}, nil } -func checkSignature(signature string, args ...string) bool { - checkSig := cookieSignature(sha256.New, args...) - if checkHmac(signature, checkSig) { - return true +// Encrypt encrypts a value with the embedded Cipher & Base64 encodes it +func (c *base64Cipher) Encrypt(value []byte) ([]byte, error) { + encrypted, err := c.Cipher.Encrypt(value) + if err != nil { + return nil, err } - // TODO: After appropriate rollout window, remove support for SHA1 - legacySig := cookieSignature(sha1.New, args...) - return checkHmac(signature, legacySig) + return []byte(base64.StdEncoding.EncodeToString(encrypted)), nil } -func checkHmac(input, expected string) bool { - inputMAC, err1 := base64.URLEncoding.DecodeString(input) - if err1 == nil { - expectedMAC, err2 := base64.URLEncoding.DecodeString(expected) - if err2 == nil { - return hmac.Equal(inputMAC, expectedMAC) - } +// Decrypt Base64 decodes a value & decrypts it with the embedded Cipher +func (c *base64Cipher) Decrypt(ciphertext []byte) ([]byte, error) { + encrypted, err := base64.StdEncoding.DecodeString(string(ciphertext)) + if err != nil { + return nil, fmt.Errorf("failed to base64 decode value %s", err) } - return false + + return c.Cipher.Decrypt(encrypted) } -// Cipher provides methods to encrypt and decrypt cookie values -type Cipher struct { +type cfbCipher struct { cipher.Block } -// NewCipher returns a new aes Cipher for encrypting cookie values -func NewCipher(secret []byte) (*Cipher, error) { +// NewCFBCipher returns a new AES CFB Cipher +func NewCFBCipher(secret []byte) (Cipher, error) { c, err := aes.NewCipher(secret) if err != nil { return nil, err } - return &Cipher{Block: c}, err + return &cfbCipher{Block: c}, err } -// Encrypt a value for use in a cookie -func (c *Cipher) Encrypt(value string) (string, error) { +// Encrypt with AES CFB +func (c *cfbCipher) Encrypt(value []byte) ([]byte, error) { ciphertext := make([]byte, aes.BlockSize+len(value)) iv := ciphertext[:aes.BlockSize] if _, err := io.ReadFull(rand.Reader, iv); err != nil { - return "", fmt.Errorf("failed to create initialization vector %s", err) + return nil, fmt.Errorf("failed to create initialization vector %s", err) } stream := cipher.NewCFBEncrypter(c.Block, iv) - stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(value)) - return base64.StdEncoding.EncodeToString(ciphertext), nil + stream.XORKeyStream(ciphertext[aes.BlockSize:], value) + return ciphertext, nil } -// Decrypt a value from a cookie to it's original string -func (c *Cipher) Decrypt(s string) (string, error) { - encrypted, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return "", fmt.Errorf("failed to decrypt cookie value %s", err) +// Decrypt an AES CFB ciphertext +func (c *cfbCipher) Decrypt(ciphertext []byte) ([]byte, error) { + if len(ciphertext) < aes.BlockSize { + return nil, fmt.Errorf("encrypted value should be at least %d bytes, but is only %d bytes", aes.BlockSize, len(ciphertext)) } - if len(encrypted) < aes.BlockSize { - return "", fmt.Errorf("encrypted cookie value should be "+ - "at least %d bytes, but is only %d bytes", - aes.BlockSize, len(encrypted)) - } - - iv := encrypted[:aes.BlockSize] - encrypted = encrypted[aes.BlockSize:] + iv, ciphertext := ciphertext[:aes.BlockSize], ciphertext[aes.BlockSize:] + plaintext := make([]byte, len(ciphertext)) stream := cipher.NewCFBDecrypter(c.Block, iv) - stream.XORKeyStream(encrypted, encrypted) + stream.XORKeyStream(plaintext, ciphertext) - return string(encrypted), nil + return plaintext, nil } -// EncryptInto encrypts the value and stores it back in the string pointer -func (c *Cipher) EncryptInto(s *string) error { - return into(c.Encrypt, s) +type gcmCipher struct { + cipher.Block } -// DecryptInto decrypts the value and stores it back in the string pointer -func (c *Cipher) DecryptInto(s *string) error { - return into(c.Decrypt, s) -} - -// codecFunc is a function that takes a string and encodes/decodes it -type codecFunc func(string) (string, error) - -func into(f codecFunc, s *string) error { - // Do not encrypt/decrypt nil or empty strings - if s == nil || *s == "" { - return nil - } - - d, err := f(*s) +// NewGCMCipher returns a new AES GCM Cipher +func NewGCMCipher(secret []byte) (Cipher, error) { + c, err := aes.NewCipher(secret) if err != nil { - return err + return nil, err } - *s = d - return nil + return &gcmCipher{Block: c}, err +} + +// Encrypt with AES GCM on raw bytes +func (c *gcmCipher) Encrypt(value []byte) ([]byte, error) { + gcm, err := cipher.NewGCM(c.Block) + if err != nil { + return nil, err + } + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + // Using nonce as Seal's dst argument results in it being the first + // chunk of bytes in the ciphertext. Decrypt retrieves the nonce/IV from this. + ciphertext := gcm.Seal(nonce, nonce, value, nil) + return ciphertext, nil +} + +// Decrypt an AES GCM ciphertext +func (c *gcmCipher) Decrypt(ciphertext []byte) ([]byte, error) { + gcm, err := cipher.NewGCM(c.Block) + if err != nil { + return nil, err + } + + nonceSize := gcm.NonceSize() + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + return plaintext, nil } diff --git a/pkg/encryption/cipher_test.go b/pkg/encryption/cipher_test.go index aed529f3..b552e70c 100644 --- a/pkg/encryption/cipher_test.go +++ b/pkg/encryption/cipher_test.go @@ -2,8 +2,6 @@ package encryption import ( "crypto/rand" - "crypto/sha1" - "crypto/sha256" "encoding/base64" "fmt" "io" @@ -12,107 +10,20 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSecretBytesEncoded(t *testing.T) { - for _, secretSize := range []int{16, 24, 32} { - t.Run(fmt.Sprintf("%d", secretSize), func(t *testing.T) { - secret := make([]byte, secretSize) - _, err := io.ReadFull(rand.Reader, secret) - assert.Equal(t, nil, err) - - // We test both padded & raw Base64 to ensure we handle both - // potential user input routes for Base64 - base64Padded := base64.URLEncoding.EncodeToString(secret) - sb := SecretBytes(base64Padded) - assert.Equal(t, secret, sb) - assert.Equal(t, len(sb), secretSize) - - base64Raw := base64.RawURLEncoding.EncodeToString(secret) - sb = SecretBytes(base64Raw) - assert.Equal(t, secret, sb) - assert.Equal(t, len(sb), secretSize) - }) - } -} - -// A string that isn't intended as Base64 and still decodes (but to unintended length) -// will return the original secret as bytes -func TestSecretBytesEncodedWrongSize(t *testing.T) { - for _, secretSize := range []int{15, 20, 28, 33, 44} { - t.Run(fmt.Sprintf("%d", secretSize), func(t *testing.T) { - secret := make([]byte, secretSize) - _, err := io.ReadFull(rand.Reader, secret) - assert.Equal(t, nil, err) - - // We test both padded & raw Base64 to ensure we handle both - // potential user input routes for Base64 - base64Padded := base64.URLEncoding.EncodeToString(secret) - sb := SecretBytes(base64Padded) - assert.NotEqual(t, secret, sb) - assert.NotEqual(t, len(sb), secretSize) - // The given secret is returned as []byte - assert.Equal(t, base64Padded, string(sb)) - - base64Raw := base64.RawURLEncoding.EncodeToString(secret) - sb = SecretBytes(base64Raw) - assert.NotEqual(t, secret, sb) - assert.NotEqual(t, len(sb), secretSize) - // The given secret is returned as []byte - assert.Equal(t, base64Raw, string(sb)) - }) - } -} - -func TestSecretBytesNonBase64(t *testing.T) { - trailer := "equals==========" - assert.Equal(t, trailer, string(SecretBytes(trailer))) - - raw16 := "asdflkjhqwer)(*&" - sb16 := SecretBytes(raw16) - assert.Equal(t, raw16, string(sb16)) - assert.Equal(t, 16, len(sb16)) - - raw24 := "asdflkjhqwer)(*&CJEN#$%^" - sb24 := SecretBytes(raw24) - assert.Equal(t, raw24, string(sb24)) - assert.Equal(t, 24, len(sb24)) - - raw32 := "asdflkjhqwer)(*&1234lkjhqwer)(*&" - sb32 := SecretBytes(raw32) - assert.Equal(t, raw32, string(sb32)) - assert.Equal(t, 32, len(sb32)) -} - -func TestSignAndValidate(t *testing.T) { - seed := "0123456789abcdef" - key := "cookie-name" - value := base64.URLEncoding.EncodeToString([]byte("I am soooo encoded")) - epoch := "123456789" - - sha256sig := cookieSignature(sha256.New, seed, key, value, epoch) - sha1sig := cookieSignature(sha1.New, seed, key, value, epoch) - - assert.True(t, checkSignature(sha256sig, seed, key, value, epoch)) - // This should be switched to False after fully deprecating SHA1 - assert.True(t, checkSignature(sha1sig, seed, key, value, epoch)) - - assert.False(t, checkSignature(sha256sig, seed, key, "tampered", epoch)) - assert.False(t, checkSignature(sha1sig, seed, key, "tampered", epoch)) -} - func TestEncodeAndDecodeAccessToken(t *testing.T) { const secret = "0123456789abcdefghijklmnopqrstuv" const token = "my access token" - c, err := NewCipher([]byte(secret)) + c, err := NewBase64Cipher(NewCFBCipher, []byte(secret)) assert.Equal(t, nil, err) - encoded, err := c.Encrypt(token) + encoded, err := c.Encrypt([]byte(token)) assert.Equal(t, nil, err) decoded, err := c.Decrypt(encoded) assert.Equal(t, nil, err) - assert.NotEqual(t, token, encoded) - assert.Equal(t, token, decoded) + assert.NotEqual(t, []byte(token), encoded) + assert.Equal(t, []byte(token), decoded) } func TestEncodeAndDecodeAccessTokenB64(t *testing.T) { @@ -121,37 +32,199 @@ func TestEncodeAndDecodeAccessTokenB64(t *testing.T) { secret, err := base64.URLEncoding.DecodeString(secretBase64) assert.Equal(t, nil, err) - c, err := NewCipher([]byte(secret)) + c, err := NewBase64Cipher(NewCFBCipher, []byte(secret)) assert.Equal(t, nil, err) - encoded, err := c.Encrypt(token) + encoded, err := c.Encrypt([]byte(token)) assert.Equal(t, nil, err) decoded, err := c.Decrypt(encoded) assert.Equal(t, nil, err) - assert.NotEqual(t, token, encoded) - assert.Equal(t, token, decoded) + assert.NotEqual(t, []byte(token), encoded) + assert.Equal(t, []byte(token), decoded) } -func TestEncodeIntoAndDecodeIntoAccessToken(t *testing.T) { - const secret = "0123456789abcdefghijklmnopqrstuv" - c, err := NewCipher([]byte(secret)) +func TestEncryptAndDecrypt(t *testing.T) { + // Test our 2 cipher types + cipherInits := map[string]func([]byte) (Cipher, error){ + "CFB": NewCFBCipher, + "GCM": NewGCMCipher, + } + for name, initCipher := range cipherInits { + t.Run(name, func(t *testing.T) { + // Test all 3 valid AES sizes + for _, secretSize := range []int{16, 24, 32} { + t.Run(fmt.Sprintf("%d", secretSize), func(t *testing.T) { + secret := make([]byte, secretSize) + _, err := io.ReadFull(rand.Reader, secret) + assert.Equal(t, nil, err) + + // Test Standard & Base64 wrapped + cstd, err := initCipher(secret) + assert.Equal(t, nil, err) + + cb64, err := NewBase64Cipher(initCipher, secret) + assert.Equal(t, nil, err) + + ciphers := map[string]Cipher{ + "Standard": cstd, + "Base64": cb64, + } + + for cName, c := range ciphers { + t.Run(cName, func(t *testing.T) { + // Test various sizes sessions might be + for _, dataSize := range []int{10, 100, 1000, 5000, 10000} { + t.Run(fmt.Sprintf("%d", dataSize), func(t *testing.T) { + runEncryptAndDecrypt(t, c, dataSize) + }) + } + }) + } + }) + } + }) + } +} + +func runEncryptAndDecrypt(t *testing.T, c Cipher, dataSize int) { + data := make([]byte, dataSize) + _, err := io.ReadFull(rand.Reader, data) assert.Equal(t, nil, err) - token := "my access token" - originalToken := token + // Ensure our Encrypt function doesn't encrypt in place + immutableData := make([]byte, len(data)) + copy(immutableData, data) - assert.Equal(t, nil, c.EncryptInto(&token)) - assert.NotEqual(t, originalToken, token) + encrypted, err := c.Encrypt(data) + assert.Equal(t, nil, err) + assert.NotEqual(t, encrypted, data) + // Encrypt didn't operate in-place on []byte + assert.Equal(t, data, immutableData) - assert.Equal(t, nil, c.DecryptInto(&token)) - assert.Equal(t, originalToken, token) + // Ensure our Decrypt function doesn't decrypt in place + immutableEnc := make([]byte, len(encrypted)) + copy(immutableEnc, encrypted) - // Check no errors with empty or nil strings - empty := "" - assert.Equal(t, nil, c.EncryptInto(&empty)) - assert.Equal(t, nil, c.DecryptInto(&empty)) - assert.Equal(t, nil, c.EncryptInto(nil)) - assert.Equal(t, nil, c.DecryptInto(nil)) + decrypted, err := c.Decrypt(encrypted) + assert.Equal(t, nil, err) + // Original data back + assert.Equal(t, data, decrypted) + // Decrypt didn't operate in-place on []byte + assert.Equal(t, encrypted, immutableEnc) + // Encrypt/Decrypt actually did something + assert.NotEqual(t, encrypted, decrypted) +} + +func TestDecryptCFBWrongSecret(t *testing.T) { + secret1 := []byte("0123456789abcdefghijklmnopqrstuv") + secret2 := []byte("9876543210abcdefghijklmnopqrstuv") + + c1, err := NewCFBCipher(secret1) + assert.Equal(t, nil, err) + + c2, err := NewCFBCipher(secret2) + assert.Equal(t, nil, err) + + data := []byte("f3928pufm982374dj02y485dsl34890u2t9nd4028s94dm58y2394087dhmsyt29h8df") + + ciphertext, err := c1.Encrypt(data) + assert.Equal(t, nil, err) + + wrongData, err := c2.Decrypt(ciphertext) + assert.Equal(t, nil, err) + assert.NotEqual(t, data, wrongData) +} + +func TestDecryptGCMWrongSecret(t *testing.T) { + secret1 := []byte("0123456789abcdefghijklmnopqrstuv") + secret2 := []byte("9876543210abcdefghijklmnopqrstuv") + + c1, err := NewGCMCipher(secret1) + assert.Equal(t, nil, err) + + c2, err := NewGCMCipher(secret2) + assert.Equal(t, nil, err) + + data := []byte("f3928pufm982374dj02y485dsl34890u2t9nd4028s94dm58y2394087dhmsyt29h8df") + + ciphertext, err := c1.Encrypt(data) + assert.Equal(t, nil, err) + + // GCM is authenticated - this should lead to message authentication failed + _, err = c2.Decrypt(ciphertext) + assert.Error(t, err) +} + +// Encrypt with GCM, Decrypt with CFB: Results in Garbage data +func TestGCMtoCFBErrors(t *testing.T) { + // Test all 3 valid AES sizes + for _, secretSize := range []int{16, 24, 32} { + t.Run(fmt.Sprintf("%d", secretSize), func(t *testing.T) { + secret := make([]byte, secretSize) + _, err := io.ReadFull(rand.Reader, secret) + assert.Equal(t, nil, err) + + gcm, err := NewGCMCipher(secret) + assert.Equal(t, nil, err) + + cfb, err := NewCFBCipher(secret) + assert.Equal(t, nil, err) + + // Test various sizes sessions might be + for _, dataSize := range []int{10, 100, 1000, 5000, 10000} { + t.Run(fmt.Sprintf("%d", dataSize), func(t *testing.T) { + data := make([]byte, dataSize) + _, err := io.ReadFull(rand.Reader, data) + assert.Equal(t, nil, err) + + encrypted, err := gcm.Encrypt(data) + assert.Equal(t, nil, err) + assert.NotEqual(t, encrypted, data) + + decrypted, err := cfb.Decrypt(encrypted) + assert.Equal(t, nil, err) + // Data is mangled + assert.NotEqual(t, data, decrypted) + assert.NotEqual(t, encrypted, decrypted) + }) + } + }) + } +} + +// Encrypt with CFB, Decrypt with GCM: Results in errors +func TestCFBtoGCMErrors(t *testing.T) { + // Test all 3 valid AES sizes + for _, secretSize := range []int{16, 24, 32} { + t.Run(fmt.Sprintf("%d", secretSize), func(t *testing.T) { + secret := make([]byte, secretSize) + _, err := io.ReadFull(rand.Reader, secret) + assert.Equal(t, nil, err) + + gcm, err := NewGCMCipher(secret) + assert.Equal(t, nil, err) + + cfb, err := NewCFBCipher(secret) + assert.Equal(t, nil, err) + + // Test various sizes sessions might be + for _, dataSize := range []int{10, 100, 1000, 5000, 10000} { + t.Run(fmt.Sprintf("%d", dataSize), func(t *testing.T) { + data := make([]byte, dataSize) + _, err := io.ReadFull(rand.Reader, data) + assert.Equal(t, nil, err) + + encrypted, err := cfb.Encrypt(data) + assert.Equal(t, nil, err) + assert.NotEqual(t, encrypted, data) + + // GCM is authenticated - this should lead to message authentication failed + _, err = gcm.Decrypt(encrypted) + assert.Error(t, err) + }) + } + }) + } } diff --git a/pkg/encryption/utils.go b/pkg/encryption/utils.go new file mode 100644 index 00000000..26be6b24 --- /dev/null +++ b/pkg/encryption/utils.go @@ -0,0 +1,106 @@ +package encryption + +import ( + "crypto/hmac" + "crypto/sha1" + "crypto/sha256" + "encoding/base64" + "fmt" + "hash" + "net/http" + "strconv" + "strings" + "time" +) + +// SecretBytes attempts to base64 decode the secret, if that fails it treats the secret as binary +func SecretBytes(secret string) []byte { + b, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(secret, "=")) + if err == nil { + // Only return decoded form if a valid AES length + // Don't want unintentional decoding resulting in invalid lengths confusing a user + // that thought they used a 16, 24, 32 length string + for _, i := range []int{16, 24, 32} { + if len(b) == i { + return b + } + } + } + // If decoding didn't work or resulted in non-AES compliant length, + // assume the raw string was the intended secret + return []byte(secret) +} + +// cookies are stored in a 3 part (value + timestamp + signature) to enforce that the values are as originally set. +// additionally, the 'value' is encrypted so it's opaque to the browser + +// Validate ensures a cookie is properly signed +func Validate(cookie *http.Cookie, seed string, expiration time.Duration) (value []byte, t time.Time, ok bool) { + // value, timestamp, sig + parts := strings.Split(cookie.Value, "|") + if len(parts) != 3 { + return + } + if checkSignature(parts[2], seed, cookie.Name, parts[0], parts[1]) { + ts, err := strconv.Atoi(parts[1]) + if err != nil { + return + } + // The expiration timestamp set when the cookie was created + // isn't sent back by the browser. Hence, we check whether the + // creation timestamp stored in the cookie falls within the + // window defined by (Now()-expiration, Now()]. + t = time.Unix(int64(ts), 0) + if t.After(time.Now().Add(expiration*-1)) && t.Before(time.Now().Add(time.Minute*5)) { + // it's a valid cookie. now get the contents + rawValue, err := base64.URLEncoding.DecodeString(parts[0]) + if err == nil { + value = rawValue + ok = true + return + } + } + } + return +} + +// SignedValue returns a cookie that is signed and can later be checked with Validate +func SignedValue(seed string, key string, value []byte, now time.Time) string { + encodedValue := base64.URLEncoding.EncodeToString(value) + timeStr := fmt.Sprintf("%d", now.Unix()) + sig := cookieSignature(sha256.New, seed, key, encodedValue, timeStr) + cookieVal := fmt.Sprintf("%s|%s|%s", encodedValue, timeStr, sig) + return cookieVal +} + +func cookieSignature(signer func() hash.Hash, args ...string) string { + h := hmac.New(signer, []byte(args[0])) + for _, arg := range args[1:] { + h.Write([]byte(arg)) + } + var b []byte + b = h.Sum(b) + return base64.URLEncoding.EncodeToString(b) +} + +func checkSignature(signature string, args ...string) bool { + checkSig := cookieSignature(sha256.New, args...) + if checkHmac(signature, checkSig) { + return true + } + + // TODO: After appropriate rollout window, remove support for SHA1 + legacySig := cookieSignature(sha1.New, args...) + return checkHmac(signature, legacySig) +} + +func checkHmac(input, expected string) bool { + inputMAC, err1 := base64.URLEncoding.DecodeString(input) + if err1 == nil { + expectedMAC, err2 := base64.URLEncoding.DecodeString(expected) + if err2 == nil { + return hmac.Equal(inputMAC, expectedMAC) + } + } + return false +} diff --git a/pkg/encryption/utils_test.go b/pkg/encryption/utils_test.go new file mode 100644 index 00000000..15bc83fe --- /dev/null +++ b/pkg/encryption/utils_test.go @@ -0,0 +1,100 @@ +package encryption + +import ( + "crypto/rand" + "crypto/sha1" + "crypto/sha256" + "encoding/base64" + "fmt" + "io" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSecretBytesEncoded(t *testing.T) { + for _, secretSize := range []int{16, 24, 32} { + t.Run(fmt.Sprintf("%d", secretSize), func(t *testing.T) { + secret := make([]byte, secretSize) + _, err := io.ReadFull(rand.Reader, secret) + assert.Equal(t, nil, err) + + // We test both padded & raw Base64 to ensure we handle both + // potential user input routes for Base64 + base64Padded := base64.URLEncoding.EncodeToString(secret) + sb := SecretBytes(base64Padded) + assert.Equal(t, secret, sb) + assert.Equal(t, len(sb), secretSize) + + base64Raw := base64.RawURLEncoding.EncodeToString(secret) + sb = SecretBytes(base64Raw) + assert.Equal(t, secret, sb) + assert.Equal(t, len(sb), secretSize) + }) + } +} + +// A string that isn't intended as Base64 and still decodes (but to unintended length) +// will return the original secret as bytes +func TestSecretBytesEncodedWrongSize(t *testing.T) { + for _, secretSize := range []int{15, 20, 28, 33, 44} { + t.Run(fmt.Sprintf("%d", secretSize), func(t *testing.T) { + secret := make([]byte, secretSize) + _, err := io.ReadFull(rand.Reader, secret) + assert.Equal(t, nil, err) + + // We test both padded & raw Base64 to ensure we handle both + // potential user input routes for Base64 + base64Padded := base64.URLEncoding.EncodeToString(secret) + sb := SecretBytes(base64Padded) + assert.NotEqual(t, secret, sb) + assert.NotEqual(t, len(sb), secretSize) + // The given secret is returned as []byte + assert.Equal(t, base64Padded, string(sb)) + + base64Raw := base64.RawURLEncoding.EncodeToString(secret) + sb = SecretBytes(base64Raw) + assert.NotEqual(t, secret, sb) + assert.NotEqual(t, len(sb), secretSize) + // The given secret is returned as []byte + assert.Equal(t, base64Raw, string(sb)) + }) + } +} + +func TestSecretBytesNonBase64(t *testing.T) { + trailer := "equals==========" + assert.Equal(t, trailer, string(SecretBytes(trailer))) + + raw16 := "asdflkjhqwer)(*&" + sb16 := SecretBytes(raw16) + assert.Equal(t, raw16, string(sb16)) + assert.Equal(t, 16, len(sb16)) + + raw24 := "asdflkjhqwer)(*&CJEN#$%^" + sb24 := SecretBytes(raw24) + assert.Equal(t, raw24, string(sb24)) + assert.Equal(t, 24, len(sb24)) + + raw32 := "asdflkjhqwer)(*&1234lkjhqwer)(*&" + sb32 := SecretBytes(raw32) + assert.Equal(t, raw32, string(sb32)) + assert.Equal(t, 32, len(sb32)) +} + +func TestSignAndValidate(t *testing.T) { + seed := "0123456789abcdef" + key := "cookie-name" + value := base64.URLEncoding.EncodeToString([]byte("I am soooo encoded")) + epoch := "123456789" + + sha256sig := cookieSignature(sha256.New, seed, key, value, epoch) + sha1sig := cookieSignature(sha1.New, seed, key, value, epoch) + + assert.True(t, checkSignature(sha256sig, seed, key, value, epoch)) + // This should be switched to False after fully deprecating SHA1 + assert.True(t, checkSignature(sha1sig, seed, key, value, epoch)) + + assert.False(t, checkSignature(sha256sig, seed, key, "tampered", epoch)) + assert.False(t, checkSignature(sha1sig, seed, key, "tampered", epoch)) +} diff --git a/pkg/sessions/cookie/session_store.go b/pkg/sessions/cookie/session_store.go index 1b88e027..62f6b348 100644 --- a/pkg/sessions/cookie/session_store.go +++ b/pkg/sessions/cookie/session_store.go @@ -28,7 +28,7 @@ var _ sessions.SessionStore = &SessionStore{} // interface that stores sessions in client side cookies type SessionStore struct { CookieOptions *options.CookieOptions - CookieCipher *encryption.Cipher + CookieCipher encryption.Cipher } // Save takes a sessions.SessionState and stores the information from it @@ -59,7 +59,7 @@ func (s *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) { return nil, errors.New("cookie signature not valid") } - session, err := sessionFromCookie(val, s.CookieCipher) + session, err := sessionFromCookie(string(val), s.CookieCipher) if err != nil { return nil, err } @@ -84,12 +84,12 @@ func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error { } // cookieForSession serializes a session state for storage in a cookie -func cookieForSession(s *sessions.SessionState, c *encryption.Cipher) (string, error) { +func cookieForSession(s *sessions.SessionState, c encryption.Cipher) (string, error) { return s.EncodeSessionState(c) } // sessionFromCookie deserializes a session from a cookie value -func sessionFromCookie(v string, c *encryption.Cipher) (s *sessions.SessionState, err error) { +func sessionFromCookie(v string, c encryption.Cipher) (s *sessions.SessionState, err error) { return sessions.DecodeSessionState(v, c) } @@ -104,7 +104,7 @@ func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Reques // authentication details func (s *SessionStore) makeSessionCookie(req *http.Request, value string, now time.Time) []*http.Cookie { if value != "" { - value = encryption.SignedValue(s.CookieOptions.Secret, s.CookieOptions.Name, value, now) + value = encryption.SignedValue(s.CookieOptions.Secret, s.CookieOptions.Name, []byte(value), now) } c := s.makeCookie(req, s.CookieOptions.Name, value, s.CookieOptions.Expire, now) if len(c.Value) > 4096-len(s.CookieOptions.Name) { diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 472ac3c9..2d0fd9b0 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -32,7 +32,7 @@ type TicketData struct { // SessionStore is an implementation of the sessions.SessionStore // interface that stores sessions in redis type SessionStore struct { - CookieCipher *encryption.Cipher + CookieCipher encryption.Cipher CookieOptions *options.CookieOptions Client Client } @@ -175,7 +175,7 @@ func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, erro return nil, fmt.Errorf("cookie signature not valid") } ctx := req.Context() - session, err := store.loadSessionFromString(ctx, val) + session, err := store.loadSessionFromString(ctx, string(val)) if err != nil { return nil, fmt.Errorf("error loading session: %s", err) } @@ -237,7 +237,7 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro // We only return an error if we had an issue with redis // If there's an issue decoding the ticket, ignore it - ticket, _ := decodeTicket(store.CookieOptions.Name, val) + ticket, _ := decodeTicket(store.CookieOptions.Name, string(val)) if ticket != nil { ctx := req.Context() err := store.Client.Del(ctx, ticket.asHandle(store.CookieOptions.Name)) @@ -251,7 +251,7 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro // makeCookie makes a cookie, signing the value if present func (store *SessionStore) makeCookie(req *http.Request, value string, expires time.Duration, now time.Time) *http.Cookie { if value != "" { - value = encryption.SignedValue(store.CookieOptions.Secret, store.CookieOptions.Name, value, now) + value = encryption.SignedValue(store.CookieOptions.Secret, store.CookieOptions.Name, []byte(value), now) } return cookies.MakeCookieFromOptions( req, @@ -302,7 +302,7 @@ func (store *SessionStore) getTicket(requestCookie *http.Cookie) (*TicketData, e } // Valid cookie, decode the ticket - ticket, err := decodeTicket(store.CookieOptions.Name, val) + ticket, err := decodeTicket(store.CookieOptions.Name, string(val)) if err != nil { // If we can't decode the ticket we have to create a new one return newTicket() diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index 60a86cef..1a60aa0d 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -170,7 +170,7 @@ var _ = Describe("NewSessionStore", func() { BeforeEach(func() { By("Using a valid cookie with a different providers session encoding") broken := "BrokenSessionFromADifferentSessionImplementation" - value := encryption.SignedValue(cookieOpts.Secret, cookieOpts.Name, broken, time.Now()) + value := encryption.SignedValue(cookieOpts.Secret, cookieOpts.Name, []byte(broken), time.Now()) cookie := cookiesapi.MakeCookieFromOptions(request, cookieOpts.Name, value, cookieOpts, cookieOpts.Expire, time.Now()) request.AddCookie(cookie) @@ -367,7 +367,7 @@ var _ = Describe("NewSessionStore", func() { _, err := rand.Read(secret) Expect(err).ToNot(HaveOccurred()) cookieOpts.Secret = base64.URLEncoding.EncodeToString(secret) - cipher, err := encryption.NewCipher(encryption.SecretBytes(cookieOpts.Secret)) + cipher, err := encryption.NewBase64Cipher(encryption.NewCFBCipher, encryption.SecretBytes(cookieOpts.Secret)) Expect(err).ToNot(HaveOccurred()) Expect(cipher).ToNot(BeNil()) opts.Cipher = cipher diff --git a/pkg/validation/options.go b/pkg/validation/options.go index 2e028677..b22882d0 100644 --- a/pkg/validation/options.go +++ b/pkg/validation/options.go @@ -38,7 +38,7 @@ func Validate(o *options.Options) error { msgs := make([]string, 0) - var cipher *encryption.Cipher + var cipher encryption.Cipher if o.Cookie.Secret == "" { msgs = append(msgs, "missing setting: cookie-secret") } else { @@ -62,7 +62,7 @@ func Validate(o *options.Options) error { len(encryption.SecretBytes(o.Cookie.Secret)), suffix)) } else { var err error - cipher, err = encryption.NewCipher(encryption.SecretBytes(o.Cookie.Secret)) + cipher, err = encryption.NewBase64Cipher(encryption.NewCFBCipher, encryption.SecretBytes(o.Cookie.Secret)) if err != nil { msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err)) }