{"__v":155,"_id":"5743211edef0df0e00e5fffa","category":{"__v":3,"_id":"56ce1e6ee538330b0021ac5d","pages":["56ce1ec2f3539413004711ee","56ce1edef3539413004711f1","56ce2072e538330b0021ac62"],"project":"55c6bec1b9aa4e0d0016c2c3","version":"55c6bec1b9aa4e0d0016c2c6","sync":{"url":"","isSync":false},"reference":false,"createdAt":"2016-02-24T21:19:42.029Z","from_sync":false,"order":1,"slug":"tools","title":"Installation"},"parentDoc":null,"project":"55c6bec1b9aa4e0d0016c2c3","user":"56f2913a2344ff0e006c0119","version":{"__v":8,"_id":"55c6bec1b9aa4e0d0016c2c6","project":"55c6bec1b9aa4e0d0016c2c3","createdAt":"2015-08-09T02:45:21.683Z","releaseDate":"2015-08-09T02:45:21.683Z","categories":["55c6bec2b9aa4e0d0016c2c7","56c14bc5826df10d00e82230","56cceed8723ad71d00cae46c","56ccf29a431ada1f00e85aae","56ccf3c28fa8b01b00b82018","56ce1e6ee538330b0021ac5d","56f97e9a4c612020008f2eaf","5734fafd146eb82000597261"],"is_deprecated":false,"is_hidden":false,"is_beta":false,"is_stable":true,"codename":"","version_clean":"1.0.0","version":"1.0"},"updates":["5744331ae6c03d0e00355cf2","5746ddc27e1a410e004cc701","574f24f62218bf0e00cf8fa5","575903cbace5c30e00cb23cc","57a219dfafc3050e000cf3e0","57a9d865972c040e00dea5d0","57c8925459cd4b0e00b88959","583dd8ea79d6151900128be5","588ba74371e51b3900e1067a","5890c87e43f74319009dbe07","589108a0455b8a3100285933"],"next":{"pages":[],"description":""},"createdAt":"2016-05-23T15:26:22.165Z","link_external":false,"link_url":"","githubsync":"","sync_unique":"","hidden":false,"api":{"results":{"codes":[]},"settings":"","auth":"required","params":[],"url":""},"isReference":false,"order":7,"body":"[block:callout]\n{\n  \"type\": \"danger\",\n  \"title\": \"This feature is in Alpha\",\n  \"body\": \"Some features will not work with authentication enabled, such as external trigger events.\"\n}\n[/block]\nThis section will discuss a number of strategies for securing your Spinnaker installation, including transport encryption and authentication.\n\n\n[block:callout]\n{\n  \"type\": \"info\",\n  \"body\": \"It is assumed throughout this document that you are accessing your Spinnaker instance by tunneling web traffic through an SSH tunnel.  \\n\\nExample SSH tunnel command:\\n\\n```\\ngcloud compute ssh my-spin-instance --ssh-flag=\\\"-L 9000:localhost:9000\\\" --ssh-flag=\\\"-L 8084:localhost:8084\\\" \\n```\",\n  \"title\": \"Assumption\"\n}\n[/block]\n### Contents\n* [Encrypting Communication with Secure Socket Layer (SSL)](/docs/securing-spinnaker#encrypting-communication-with-secure-socket-layer-)\n* [Authenticating Users](/docs/securing-spinnaker#authenticating-users) with OAuth, SAML, LDAP, and X.509 Certificates\n* [Authorizing Users](/docs/securing-spinnaker#authorizing-users) with Google Groups\n* [Configuring Session Timeout](/docs/securing-spinnaker#configuring-session-timeout)\n* [Troubleshooting](/docs/securing-spinnaker#troubleshooting)\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Encrypting Communication with Secure Socket Layer (SSL)\"\n}\n[/block]\nThis section will cover communication external to your Spinnaker instance. That is, any requests between your browser and the Deck (the UI) host, between Deck and Gate (the API gateway), and between any other client and Gate.\n\nWe will use `openssl` to generate a Certificate Authority (CA) and a server certificate.\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"SSL Certificates\",\n  \"body\": \"For the purposes of this tutorial, we'll use a self-signed CA. You may consider using an external CA to minimize browser configuration, but it's not necessary (and can be expensive).\"\n}\n[/block]\n### Certificate Authority\nUse the steps below to create a certificate authority. If you're using an external CA, skip to the next section. \n\n1. Create the CA key.\n```\nopenssl genrsa -des3 -out ca.key 4096\n```\n2. Self-sign the CA certificate.\n```\nopenssl req -new -x509 -days 365 -key ca.key -out ca.crt\n```\n\n### Server Certificate\n1. Create the server key. Keep this file safe!\n```\nopenssl genrsa -des3 -out server.key 4096\n```\n2. Generate a certificate signing request for the server. Specify `localhost` or Gate's eventual fully-qualified domain name (FQDN) as the Common Name (CN). \n```\nopenssl req -new -key server.key -out server.csr\n```\n3. Use the CA to sign the server's request. If using an external CA, they will do this for you.\n```\nopenssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt\n```\n4. Format server certificate into Java Keystore (JKS) importable form\n```\nopenssl pkcs12 -export -clcerts -in server.crt -inkey server.key -out server.p12 -name spinnaker -password pass:$YOUR_KEY_PASSWORD\n```\nThis will create a p12 keystore file with your certificate imported under the alias \"spinnaker\" with the key password $YOUR_KEY_PASSWORD.\n5. Create Java Keystore by importing CA certificate\n```\nkeytool -keystore keystore.jks -import -trustcacerts -alias ca -file ca.crt\n```\n6. Import server certificate\n```\nkeytool -importkeystore -srckeystore server.p12 -srcstoretype pkcs12 -srcalias spinnaker -srcstorepass $YOUR_KEY_PASSWORD -destkeystore keystore.jks -deststoretype jks -destalias server -deststorepass $YOUR_KEY_PASSWORD -destkeypass $YOUR_KEY_PASSWORD\n```\n\nVoilà! You now have a Java Keystore with your certificate authority and server certificate ready to be used by Spinnaker!\n\n## Gate Configuration\nYou need to configure Gate to read and use this file. We'll assume `keystore.jks` is in the same directory as our Gate configuration files. \n\nIn the Spinnaker configuration file directory (`/opt/spinnaker/config`), create a `gate-local.yml` file and use a similar configuration as the one below:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"server:\\n  ssl:\\n    enabled: true\\n    keyStore: /opt/spinnaker/config/keystore.jks\\n    keyStorePassword: hunter2 # whatever $YOUR_KEY_PASSWORD is\\n    keyAlias: server\\n\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/gate-local.yml\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Note\",\n  \"body\": \"Due to an [implementation limitation](https://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html#Prepare_the_Certificate_Keystore) of Tomcat, which Gate uses as its http server, the key password and keystore password on your keystore must be the same.  If you're using a self-signed certificate, the key password is the `-password pass:$YOUR_PASSWORD` argument to `openssl pkcs12` in step 4.\"\n}\n[/block]\nRestart Gate and confirm `https://localhost:8084/` is available\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Restarting individual components\",\n  \"body\": \"You can restart an individual component by passing it as the solo argument to the start and stop scripts:\\n```\\nsudo /opt/spinnaker/bin/stop_spinnaker.sh gate\\nsudo /opt/spinnaker/bin/start_spinnaker.sh gate\\n```\"\n}\n[/block]\n## Deck Configuration\nSince Deck is served as static files from Apache, we must configure Apache to serve those files over HTTPS. \n\n1. Enable `mod-ssl` on Apache\n```\nsudo a2enmod ssl\n```\n2. Edit your `/etc/apache2/sites-available/spinnaker.conf` file to add your server certificate and key\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"<VirtualHost 127.0.0.1:9000>\\n  SSLEngine on\\n  SSLCertificateFile \\\"/opt/spinnaker/config/server.crt\\\"\\n  SSLCertificateKeyFile \\\"/opt/spinnaker/config/server.key\\\"\\n  \\n  # If proxying other services on this instance, add these:\\n  SSLProxyEngine On\\n  SSLProxyVerify none\\n  SSLProxyCheckPeerCN off\\n\\n  # SSL from apache (deck) to gate on the local system is fine,  \\n  # we don't really need to check gate's cert... but if we did,  \\n  # we should look into SSLProxyCACertificateFile  \\n  \\n  // ... rest of file omitted\\n</VirtualHost>\",\n      \"language\": \"xml\",\n      \"name\": \"/etc/apache2/sites-available/spinnaker.conf\"\n    }\n  ]\n}\n[/block]\n3.  Point Deck to the newly secured Gate endpoint. We'll use the preferred way of generating the `settings.js`, rather than editing it directly. In your `spinnaker-local.yml` file:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"services:\\n  deck:\\n    gateUrl: https://localhost:8084\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/spinnaker-local.yml\"\n    }\n  ]\n}\n[/block]\n4. Regenerate the `settings.js` file.\n```\nsudo /opt/spinnaker/bin/reconfigure_spinnaker.sh\n```\n5. Restart Deck\n```\n$ sudo service apache2 restart\nApache needs to decrypt your SSL Keys for localhost:9000 (RSA)\nPlease enter passphrase:\n```\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Authenticating Users\"\n}\n[/block]\nAuthentication is proving that a user is who they say they are. Spinnaker has many other responsibilities, so we prefer to delegate this need to an external identity provider. This has the added advantage of not requiring users to create and remember yet another username/password combination.\n\nSpinnaker supports 3 forms of user authentication (some with authorization capabilities):\n* OAuth 2.0\n* SAML 2.0\n* X.509 Certificates\n\n# OAuth 2.0\nSpinnaker includes OAuth 2.0 profile templates for Google (`googleOAuth`), Azure (`azureOAuth`), and GitHub (`githubOAuth`). This is achieved using Spring profiles, which we will configure and activate below. Any of the settings in the pre-configured profiles can be overridden in your `gate-<providerProfile>.yml` file. We will use the Google provider in this tutorial.\n\n##  OAuth Provider\n1. Navigate to [https://console.developers.google.com/apis/credentials](https://console.developers.google.com/apis/credentials).\n2. Create a new OAuth client ID.\n3. Select \"Web Application\", and enter a name.\n4. Under \"Authorized redirect URIs\", add `https://localhost:8084/login`, replacing \"localhost\" with your Gate hostname, if applicable. Click Create.\n5. Note the generated client ID and client secret. Copy these to a safe place (like the config file you'll create in the next section).\n\n\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/tGGYpBY9Q0akkjGDlrwO_y22f2ycaxLR.png\",\n        \"y22f2ycaxLR.png\",\n        \"759\",\n        \"761\",\n        \"#3e5883\",\n        \"\"\n      ],\n      \"caption\": \"Google OAuth Client ID creation screen.\"\n    }\n  ]\n}\n[/block]\n## Gate Configuration\n1. Create a `gate-googleOAuth.yml` file in `/opt/spinnaker/config/`. These values can be obtained by creating a new OAuth Client ID using the [Cloud Developers Console](https://console.developers.google.com/apis/credentials).\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"spring:\\n  oauth2:\\n    client:\\n      clientId: my-client-id\\n      clientSecret: ssshhh-its-a-sekret\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/gate-googleOAuth.yml\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Locking down access with UserInfoRequirements\",\n  \"body\": \"You can add a `userInfoRequirements` section to enforce the `userInfo` response to contain certain fields. This can be used as a basic form of authorization. For example, you can lock down you Google Apps for Work account to just those users within your organization by adding the `hd` (for \\\"hosted domain\\\") parameter below:\\n```  \\nspring:\\n  oauth2:\\n    client:\\n      clientId: my-client-id\\n      clientSecret: ssshhh-its-a-sekret\\n    userInfoRequirements:\\n      hd: my-domain.net\\n```\"\n}\n[/block]\n2. Activate the `googleOAuth` profile by adding an entry in `/etc/default/spinnaker`\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"# Spinnaker system defaults\\n# ... file omitted\\n\\nGATE_OPTS=\\\"-Dspring.profiles.active=local,googleOAuth\\\"\",\n      \"language\": \"shell\",\n      \"name\": \"/etc/default/spinnaker\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"body\": \"Be sure to include the `local` profile!\"\n}\n[/block]\n## Deck Configuration\n1. Enable authentication in Deck via `spinnaker-local.yml`. It is also required to specify the user-accessible address of Deck\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"services:\\n  deck:\\n    baseUrl: https://localhost:9000\\n    auth:\\n      enabled: true\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/spinnaker-local.yml\"\n    }\n  ]\n}\n[/block]\n2. Generate the new `settings.js` file for Deck:\n```\nsudo /opt/spinnaker/bin/reconfigure_spinnaker.sh\n```\n3. Restart Gate.\n4. Navigate to Deck (generally `https://localhost:9000`). Notice the \"Authenticating\" screen that then redirects to the Google.\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/z8ARmUqhR7GfoC0FSSuT_auth.png\",\n        \"auth.png\",\n        \"434\",\n        \"454\",\n        \"#fbfbfb\",\n        \"\"\n      ],\n      \"sizing\": \"smart\",\n      \"border\": false,\n      \"caption\": \"Deck beginning the authentication flow.\"\n    }\n  ]\n}\n[/block]\n\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/epetJEQmQgSpTh6O0r8U_glogin.png\",\n        \"glogin.png\",\n        \"319\",\n        \"491\",\n        \"#4c8bf2\",\n        \"\"\n      ],\n      \"sizing\": \"smart\",\n      \"caption\": \"Google account login for OAuth 2.0 Authentication.\"\n    }\n  ]\n}\n[/block]\n## Bring-Your-Own OAuth Provider\n\nIf you'd like to configure your own OAuth provider, you'll need to provide the following configuration values in your `gate-local.yml` file:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"spring:\\n  oauth2:\\n    client:\\n      clientId:\\n      clientSecret:\\n      userAuthorizationUri: # Used to get an authorization code\\n      accessTokenUri: # Used to get an access token\\n      scope:\\n    resource:\\n      userInfoUri: # Used to the current user's profile\\n    userInfoMapping: # Used to map the userInfo response to our User\\n      email: \\n      firstName:\\n      lastName:\\n      username:\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/gate-local.yml\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"title\": \"OAuth Provider Setup\",\n  \"body\": \"Most OAuth provider's required a `redirect_uri` to be set during client ID creation. When required, the value should point to `https://localhost:8084/login`, or the hostname of your Gate instance instead of `localhost`.\"\n}\n[/block]\n#  SAML 2.0\nSpinnaker also provides SAML support. If you previously had SAML configured, please consult the [Gate SAML Authentication Migration Guide](doc:gate-saml-config). \n\n## Identity Provider Configuration\nThese are the general steps to take on your Identity Provider's Administrative console.\n\n1. Download the `metadata.xml` file from the identity provider. It should look something like\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\" standalone=\\\"no\\\"?>\\n<md:EntityDescriptor \\n    xmlns:md=\\\"urn:oasis:names:tc:SAML:2.0:metadata\\\"    \\n    entityID=\\\"https://accounts.google.com/o/saml2?idpid=SomeValueHere\\\" \\n    validUntil=\\\"2021-05-16T15:17:27.000Z\\\">\\n  <md:IDPSSODescriptor \\n      WantAuthnRequestsSigned=\\\"false\\\" \\n      protocolSupportEnumeration=\\\"urn:oasis:names:tc:SAML:2.0:protocol\\\">\\n    <md:KeyDescriptor use=\\\"signing\\\">\\n      <ds:KeyInfo xmlns:ds=\\\"http://www.w3.org/2000/09/xmldsig#\\\">\\n        <ds:X509Data>\\n          <ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAVS/Sw5yMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ\\nbmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv\\nb2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTYwNTE3\\nMTUxNzI3WhcNMjEwNTE2MTUxNzI3WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN\\nTW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx\\nCzAJBgNVBAYTblVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEF46OCAQ8A\\nMIIBCgKCAQEA4JsnpS0ZBzb7DtlU7Zop7l+Kgr7NzusKWcEC6MOsFa4Dlt7jxv4ScKZ/61M5WKxd\\n5YX0ol1rPokpNztj+Zk7OXrG8lDic0DpeDutc9pcq0+9/NYFF7WR7TDjh4B7Txnq7SerSB78fT8d\\n4rK7Bd+cu/cBIyAAyZ5tLeLbmTnHAk093Y9vF3mdWQnfAhx4ldOfstF6G/d2ev7I5xjSKzQuH6Ew\\n3bb3HLcM4uEVevOfNAlh1KoV4vQr+qzbc9UEFcPRwzuTwGa6QjfspWW7NgXKbHHC+X6a+gqJrke/\\n6l2VvHaQBJ7oIyt4PCdel2cnUkvuxvzHPYedh1AgrIiSP1brSQIDAQABMA0GCSqGSI34DQEBCwUA\\nA4IBAQCPqMAIau+pRDs2NZG1nGfyEMDfs0qop6FBa/wTNis75tLvay9MUlxXkTxm9aVxgggjEyc6\\nXtDjpV0onrH0jBnSc+vRI1GFQ48EO3owy3uBIeR1aMy13ZwAA+KVizeoOrXBJbvIUZHo0yfKRzIu\\ngtM58j58BdAFeYo+X9ds/ysvZ8FIGTLqMl/A3oO/yBNDjXR9Izoqgm7RX0JJXGL9Y1AgmEjxyqo9N\\nMhxZAGxOHm9HZWWfVMcoe8p62mRJ2zf4lkNPBnDHrQ8MDPSsXewAuiSnRBDLxhdBgyThT/KW7Q06\\nrGa6Dp0rntKWzZE3hGQS0AdsnuFY/OXbmkNG9WUrUg5x</ds:X509Certificate>\\n        </ds:X509Data>\\n      </ds:KeyInfo>\\n    </md:KeyDescriptor>\\n    <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>\\n    <md:SingleSignOnService \\n        Binding=\\\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\\\" \\n        Location=\\\"https://accounts.google.com/o/saml2/idp?idpid=SomeValueHere\\\"/>\\n    <md:SingleSignOnService \\n        Binding=\\\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\\\" \\n        Location=\\\"https://accounts.google.com/o/saml2/idp?idpid=SomeValueHere\\\"/>\\n  </md:IDPSSODescriptor>\\n</md:EntityDescriptor>\",\n      \"language\": \"xml\"\n    }\n  ]\n}\n[/block]\n2. Create a Spinnaker SAML application.\n3. Specify the login URL as `https://localhost:8084/saml/SSO`.\n4. Specify a unique entity ID (we'll use `io.spinnaker:test` in our example).\n5. Enable the users you'd like to have access to your Spinnaker instance.\n\n## Gate Configuration\n1. Generate a keystore and key in a new Java Keystore with some password:\n```\nkeytool -genkey -v -keystore saml.jks -alias saml -keyalg RSA -keysize 2048 -validity 10000\n```\n2. Create or modify your `gate-local.yml` file to include the following settings:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"saml:\\n  enabled: true\\n  metadataUrl: file:/opt/spinnaker/config/metadata.xml\\n  keyStore: file:/opt/spinnaker/config/saml.jks\\n  keyStorePassword: hunter2\\n  keyStoreAliasName: saml\\n  issuerId: io.spinnaker:test\\n  redirectHostname: localhost:8084\\n\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/gate-local.yml\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"body\": \"Remove any OAuth 2.0 settings if present - OAuth and SAML are mutually exclusive authentication mechanisms and cannot coexist.\"\n}\n[/block]\n## Deck Configuration\n1. Enable authentication in Deck via `spinnaker-local.yml`. It is also required to specify the user-accessible address of Deck.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"services:\\n  deck:\\n    baseUrl: https://localhost:9000\\n    auth:\\n      enabled: true\",\n      \"language\": \"text\",\n      \"name\": \"/opt/spinnaker/config/spinnaker-local.yml\"\n    }\n  ]\n}\n[/block]\n2. Generate the new `settings.js` file for Deck:\n```\nsudo /opt/spinnaker/bin/reconfigure_spinnaker.sh\n```\n3. Restart Gate.\n4. Navigate to Deck (generally `https://localhost:9000`). Notice the \"Authenticating\" screen that then redirects to your SAML Provider.\n\n\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/kZLq0mstSmCO1H8Nuc1y_auth.png\",\n        \"auth.png\",\n        \"434\",\n        \"454\",\n        \"#fbfbfb\",\n        \"\"\n      ],\n      \"caption\": \"Deck beginning the authentication flow.\"\n    }\n  ]\n}\n[/block]\n\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/MIoEORFSHy0ZcBLXG9L1_oktaAuth.png\",\n        \"oktaAuth.png\",\n        \"663\",\n        \"513\",\n        \"#3d7798\",\n        \"\"\n      ],\n      \"caption\": \"Sample SAML login using Okta as the Identity Provider.\"\n    }\n  ]\n}\n[/block]\n# LDAP Authentication\n\nLightweight Directory Access Protocol (LDAP) is a standard way many organizations maintain user credentials and group memberships. Spinnaker uses the standard \"bind\" approach for user authentication. This is a fancy way of saying that Gate passes your username and password to the LDAP server, and if the connection is successful, you're considered authenticated.\n\nLDAP directories are generally organized by first defining a \"root\" distinguished name (DN). User DNs are constructed by combining this root DN with a user DN pattern. \n\nFor example, if your root DN is `dc=my-organization,dc=com` and your user pattern is `uid={0},ou=users`, and user with the id `joe` would have a full, unique DN of `uid=joe,ou=users,dc=my-organization,dc=com`. \n\nWhen `joe` is trying to login, this full user DN is constructed and passed to the LDAP server with his password. The password is hashed on the server and compared to its own hashed version. If successful, the bind is successful and a session is established. \n\nWe highly suggest the use of SSL for the LDAP connection (over `ldaps`). Otherwise, **user passwords are passed in clear text over the wire.**\n\nHere's a sample of the configuration that accomplishes the above example:\n\n```yaml\n# /opt/spinnaker/config/gate-local.yml\nldap:\n  enabled: true\n  url: ldaps://ldap.my-organization.com:10636/dc=my-organization,dc=com\n  userDnPattern: uid={0},ou=users\n```\nIt is also possible to use `ldap.userSearchBase` and `ldap.userSearchFilter` if the simpler `ldap.userDnPattern` does not match what your organization uses for `userDn`s. We won't explore this use case here, but you can read up more on LDAP search filters [here](https://confluence.atlassian.com/display/DEV/How+to+write+LDAP+search+filters).\n\n## Authorization\nSee the [Fiat setup](http://www.spinnaker.io/v1.0/docs/fiat-setup#section-ldap) documentation for establishing authorizations based on LDAP group membership.  \n\n# Deck Configuration\nJust as with OAuth and SAML, you'll need to configure Deck to go through it's auth workflow. You can do this in `spinnaker-local` and the `/opt/spinnaker/bin/reconfigure_spinnaker.sh` script for VM images:\n```\nservices:\n  deck:\n    auth:\n      enabled: true\n```\n\nOr by setting `AUTH_ENABLED=true` when running Deck in a container environment, such as Kubernetes.\n\n# X.509\nClient certificates are the last way to authenticate users. It is the preferred way to access Spinnaker's API endpoints via scripts. Remember that Certificate Authority (CA) we created in the [Part 1](#section-certificate-authority)? We'll use it to create a new client certificate. \n\n## Client Certificate\n1. Create the client key. Keep this file safe!\n```\nopenssl genrsa -des3 -out client.key 4096\n```\n2. Generate a certificate signing request for the server.\n```\nopenssl req -new -key client.key -out client.csr\n```\n3. Use the CA to sign the server's request. If using an external CA, they will do this for you.\n```\nopenssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt\n```\n4. Format the client certificate into browser importable form.\n```\nopenssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12\n```\n\n## Browser Configuration\n1. In Google Chrome, navigate to [chrome://settings/certificates](chrome://settings/certificates), click the `Authorities` tab, and import the CA.\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/nV3yLHOmQUSybp2JJ41e_hys5KjMaXKe.png\",\n        \"hys5KjMaXKe.png\",\n        \"701\",\n        \"491\",\n        \"#de4315\",\n        \"\"\n      ],\n      \"caption\": \"Import via the `Authorities` tab of chrome://settings/certificates\"\n    }\n  ]\n}\n[/block]\n2.  Click the `Your Certificates` tab, and import your newly signed client certificate (`client.crt\n\n## Gate Configuration\nEnable X.509 in `gate-local.yml` with the following `x509` tree as well as the new trustStore settings. Restart Gate after making these changes.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"x509:\\n  enabled: true\\n  subjectPrincipalRegex: EMAILADDRESS=(.*?)(?:,|$) # optional\\n  \\nserver:\\n  ssl:\\n    enabled: true\\n    keyStore: /opt/spinnaker/config/keystore.jks\\n    keyStorePassword: hunter2\\n    keyAlias: server\\n    trustStore: /opt/spinnaker/config/keystore.jks\\n    trustStorePassword: hunter2\\n    \\ndefault:\\n  apiPort: 8083\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/gate-local.yml\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"info\",\n  \"body\": \"This works because we also imported the CA in the Keystore during our SSL setup alongside the server key.\"\n}\n[/block]\n## Multiple Authentication Mechanisms\n\nX.509 can be enabled independent of OAuth  and SAML. In this scenario, users hitting Deck will be prompted through the OAuth/SAML Identity Provider login screen, and API users (scripts) can simultaneously make calls with X.509 certificates.\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Authorizing Users\"\n}\n[/block]\nThe current security model allows all authenticated users to see all resources (read access) within all accounts and applications. If enabled, specific accounts can be locked down to only allow users who are members of a certain group to perform mutating operations (write access). As of this writing, only groups from a SAML identity provider or Google Groups within your Google Apps for Work account are supported.\n\n## Google Apps for Work (GAFW)\nA prerequisite for authorization using Google Groups is a Google Apps for Work account. You can [sign up here](https://apps.google.com) if your organization does not have one.\n\n### Assign groups\n\n1. In the [Admin Console](https://admin.google.com), open the left navigation and click \"Groups.\" Here, you can add groups that specify various roles in Spinnaker.\n2. Create a new group with the plus icon and give it a name.\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"body\": \"It is suggested you change the access level of this group/role to either \\\"Team\\\" or \\\"Restricted.\\\" Leaving the default (\\\"Public\\\") means any user in your organization can join this group without prior approval.\",\n  \"title\": \"Access Level\"\n}\n[/block]\n3. Add users to your newly created group.\n\n### Configure Domain-wide Delegation\n\nGate uses a Google service account to access the Admin APIs on your behalf. We will create and configure this service account below. If you are running on Google Compute Engine (GCE) and already have a service account, you can reuse the same service account instead of creating a new one.\n\n1. Follow the instructions to [create the service account and its JSON credentials](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#create_the_service_account_and_its_credentials).\n2. Follow the instructions to [delegate Domain-wide access to the service account](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account). Your service account will need _ONLY_  the **https://www.googleapis.com/auth/admin.directory.group.readonly** scope.\n3. Make sure you [enable the Admin SDK](https://console.cloud.google.com/apis/api/admin/overview).\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Private Key\",\n  \"body\": \"Ensure to download the service account key in JSON format. Transfer this file to your Spinnaker instance with the `gcloud copy-files` command.\"\n}\n[/block]\n### Configure Gate\n\nNow that we have all the Google setup finished, we need to configure Spinnaker to\nenforce the roles we have set up. In your Spinnaker instance, add the following\nsection to your `gate-local.yml` file:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"auth:\\n  groupMembership:\\n    service: google\\n    google:\\n      credentialPath: /path/to/JSON.key\\n      adminUsername: admin:::at:::mydomain.com\\n      domain: mydomain.com\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/gate-local.yml\"\n    }\n  ]\n}\n[/block]\nOnce you have this configuration complete, restart Spinnaker and enjoy your enforced authN/Z roles!\nEach time you log into Spinnaker, you will be redirected to a Google sign-in page if you do not currently have a session. If you do have a session, Gate will enforce your required group memberships to access Spinnaker resources.\n\n### Configure Clouddriver\nThe last step is to ensure Clouddriver knows to restricted access for particular accounts to those users with access. If you specify multiple groups, a user only needs to be a member of *one* of the specified groups to be granted access. \n\nCreate a `clouddriver-local.yml` file to override and enhance the `clouddriver.yml` file from the [spinnaker/spinnaker config](https://github.com/spinnaker/spinnaker/blob/master/config/clouddriver.yml). An example is shown below:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"google:\\n  accounts:\\n  - name: my-protected-google-account\\n    project: my-spinnaker-project\\n    jsonPath: /opt/spinnaker/config/gafw-service-account.json\\n    requiredGroupMembership:\\n    - spinnaker-users\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/clouddriver-local.yml\"\n    }\n  ]\n}\n[/block]\n## Test Driving your changes\nRestart Gate and Clouddriver to have these changes take effect. If everything is configured correctly, mutating calls, such as \"Terminate Instances\" should return an error in the UI like below.\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/dGZa7AIaTtKGFXcRXCrY_authdenied%20(1).png\",\n        \"authdenied (1).png\",\n        \"724\",\n        \"507\",\n        \"#1e434e\",\n        \"\"\n      ]\n    }\n  ]\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Configuring Session Timeout\"\n}\n[/block]\nBy default, a user's session will expire after 30 minutes of inactivity. You can change this duration by adding an entry in the `gate.yml` file:\n\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"server:\\n  session:\\n    timeoutInSeconds: 3600\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/gate-local.yml\"\n    }\n  ]\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Troubleshooting\"\n}\n[/block]\n### Something's not right. How do I turn on debug logs?\nAdd the following to your configuration to turn on logging. \n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"logging:\\n  level:\\n    com.netflix.spinnaker.gate.security: DEBUG\\n    org.springframework.security: DEBUG\\n    org.springframework.web: DEBUG\",\n      \"language\": \"yaml\",\n      \"name\": \"gate-local.yml\"\n    }\n  ]\n}\n[/block]\n### I see Deck requesting `/auth/info` and hanging. What gives?\n\nThis is the old endpoint. You'll need to update your `settings.js` file. There are two ways to do this.\n1. Edit your `/opt/spinnaker/config/settings.js` file and change the `authEndpoint` to: \n\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"window.spinnakerSettings = {\\n  // ...\\n  authEndpoint: gateUrl + '/auth/user',\\n  //...\\n}\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nand run this to regenerate the `/opt/deck/html/settings.js` file:\n```\nsudo /opt/spinnaker/bin/reconfigure_spinnaker.sh\n```\n\n2. Update your `AUTH_ENDPOINT` environment var to end with `/auth/user`. This is likely applicable if you're using Docker Compose or a development instance.\n\n---\n\n### I'm getting an `Error: redirect_uri_mismatch` from my OAuth provider.\n\nThe full error may look something like:\n```\nError: redirect_uri_mismatch. The redirect URI in the request, https://<some_url>/login, \ndoes not match the ones authorized for the OAuth client.\n```\n\nThis likely means you've not set up your OAuth credentials correctly. Ensure that the Authorized Request URIs list contains \"http**s**://my-gate-address/login\" (no trailing /).\n\nIn the case that this is set correctly, see the next answer.\n\n---\n\n### I terminate SSL connections outside of Gate, and my `redirect_uri` configuration is correct (see above). How do I make this work?\n\nBy default, Gate will use its own protocol, host, and port when constructing the `redirect_uri`. In the case where you want to terminate SSL connections outside of Gate, you'll need to override this by providing the exact address in your gate-<provider>.yml:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"spring:\\n  oauth2:\\n    client:\\n      preEstablishedRedirectUri: https://my-real-gate-address.com/login\\n      useCurrentUri: false\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/gate-<provider>.yml\"\n    }\n  ]\n}\n[/block]\n\n ---\n\n### I'm able to login to my identity provider, but afterwards I get a `Requested redirect address not recognized` error\n\nIn your `spinnaker-local.yml` file, you need to set `services.deck.baseUrl` to the address of Deck. We only allow redirects to URLs that match the protocol, host, and port of the `services.deck.baseUrl`. For example:  \n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"services:\\n  deck:\\n    baseUrl: https://localhost:9000\",\n      \"language\": \"yaml\",\n      \"name\": \"/opt/spinnaker/config/spinnaker-local.yml\"\n    }\n  ]\n}\n[/block]\n### I have deck behind an external load balancer.  During authentication, it redirects to port :9000 instead of the port the LB is listening on.\n\nWhen reverse proxying, Apache will try to determine your host/port from the incoming request.  If the port isn't specified, it will write in the port it's listening on (which for deck is 9000 by default).  In your apache configuration file,  add the following section.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"<VirtualHost *:9000>\\n  # ServerName https://my_deck_hostname:externally_visible_port\\n  # For instance, if your LB is listening on \\n  # https://spinnaker.mydomain.com:443 and forwarding that to 9000, you want:\\n  ServerName https://spinnaker.mydomain.com:443\\n  UseCanonicalName On\\n  \\n  ... rest of file omitted\\n</VirtualHost>\",\n      \"language\": \"xml\",\n      \"name\": \"/etc/apache2/sites-available/spinnaker.conf\"\n    }\n  ]\n}\n[/block]\nBy turning this setting on, you force Apache to rewrite the URL using the ServerName instead of trying to infer it from the request.  Alternately, [this issue](https://github.com/spinnaker/spinnaker/issues/997) may be of help.","excerpt":"","slug":"securing-spinnaker","type":"basic","title":"Securing Spinnaker"}

Securing Spinnaker


[block:callout] { "type": "danger", "title": "This feature is in Alpha", "body": "Some features will not work with authentication enabled, such as external trigger events." } [/block] This section will discuss a number of strategies for securing your Spinnaker installation, including transport encryption and authentication. [block:callout] { "type": "info", "body": "It is assumed throughout this document that you are accessing your Spinnaker instance by tunneling web traffic through an SSH tunnel. \n\nExample SSH tunnel command:\n\n```\ngcloud compute ssh my-spin-instance --ssh-flag=\"-L 9000:localhost:9000\" --ssh-flag=\"-L 8084:localhost:8084\" \n```", "title": "Assumption" } [/block] ### Contents * [Encrypting Communication with Secure Socket Layer (SSL)](/docs/securing-spinnaker#encrypting-communication-with-secure-socket-layer-) * [Authenticating Users](/docs/securing-spinnaker#authenticating-users) with OAuth, SAML, LDAP, and X.509 Certificates * [Authorizing Users](/docs/securing-spinnaker#authorizing-users) with Google Groups * [Configuring Session Timeout](/docs/securing-spinnaker#configuring-session-timeout) * [Troubleshooting](/docs/securing-spinnaker#troubleshooting) [block:api-header] { "type": "basic", "title": "Encrypting Communication with Secure Socket Layer (SSL)" } [/block] This section will cover communication external to your Spinnaker instance. That is, any requests between your browser and the Deck (the UI) host, between Deck and Gate (the API gateway), and between any other client and Gate. We will use `openssl` to generate a Certificate Authority (CA) and a server certificate. [block:callout] { "type": "info", "title": "SSL Certificates", "body": "For the purposes of this tutorial, we'll use a self-signed CA. You may consider using an external CA to minimize browser configuration, but it's not necessary (and can be expensive)." } [/block] ### Certificate Authority Use the steps below to create a certificate authority. If you're using an external CA, skip to the next section. 1. Create the CA key. ``` openssl genrsa -des3 -out ca.key 4096 ``` 2. Self-sign the CA certificate. ``` openssl req -new -x509 -days 365 -key ca.key -out ca.crt ``` ### Server Certificate 1. Create the server key. Keep this file safe! ``` openssl genrsa -des3 -out server.key 4096 ``` 2. Generate a certificate signing request for the server. Specify `localhost` or Gate's eventual fully-qualified domain name (FQDN) as the Common Name (CN). ``` openssl req -new -key server.key -out server.csr ``` 3. Use the CA to sign the server's request. If using an external CA, they will do this for you. ``` openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt ``` 4. Format server certificate into Java Keystore (JKS) importable form ``` openssl pkcs12 -export -clcerts -in server.crt -inkey server.key -out server.p12 -name spinnaker -password pass:$YOUR_KEY_PASSWORD ``` This will create a p12 keystore file with your certificate imported under the alias "spinnaker" with the key password $YOUR_KEY_PASSWORD. 5. Create Java Keystore by importing CA certificate ``` keytool -keystore keystore.jks -import -trustcacerts -alias ca -file ca.crt ``` 6. Import server certificate ``` keytool -importkeystore -srckeystore server.p12 -srcstoretype pkcs12 -srcalias spinnaker -srcstorepass $YOUR_KEY_PASSWORD -destkeystore keystore.jks -deststoretype jks -destalias server -deststorepass $YOUR_KEY_PASSWORD -destkeypass $YOUR_KEY_PASSWORD ``` Voilà! You now have a Java Keystore with your certificate authority and server certificate ready to be used by Spinnaker! ## Gate Configuration You need to configure Gate to read and use this file. We'll assume `keystore.jks` is in the same directory as our Gate configuration files. In the Spinnaker configuration file directory (`/opt/spinnaker/config`), create a `gate-local.yml` file and use a similar configuration as the one below: [block:code] { "codes": [ { "code": "server:\n ssl:\n enabled: true\n keyStore: /opt/spinnaker/config/keystore.jks\n keyStorePassword: hunter2 # whatever $YOUR_KEY_PASSWORD is\n keyAlias: server\n", "language": "yaml", "name": "/opt/spinnaker/config/gate-local.yml" } ] } [/block] [block:callout] { "type": "info", "title": "Note", "body": "Due to an [implementation limitation](https://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html#Prepare_the_Certificate_Keystore) of Tomcat, which Gate uses as its http server, the key password and keystore password on your keystore must be the same. If you're using a self-signed certificate, the key password is the `-password pass:$YOUR_PASSWORD` argument to `openssl pkcs12` in step 4." } [/block] Restart Gate and confirm `https://localhost:8084/` is available [block:callout] { "type": "info", "title": "Restarting individual components", "body": "You can restart an individual component by passing it as the solo argument to the start and stop scripts:\n```\nsudo /opt/spinnaker/bin/stop_spinnaker.sh gate\nsudo /opt/spinnaker/bin/start_spinnaker.sh gate\n```" } [/block] ## Deck Configuration Since Deck is served as static files from Apache, we must configure Apache to serve those files over HTTPS. 1. Enable `mod-ssl` on Apache ``` sudo a2enmod ssl ``` 2. Edit your `/etc/apache2/sites-available/spinnaker.conf` file to add your server certificate and key [block:code] { "codes": [ { "code": "<VirtualHost 127.0.0.1:9000>\n SSLEngine on\n SSLCertificateFile \"/opt/spinnaker/config/server.crt\"\n SSLCertificateKeyFile \"/opt/spinnaker/config/server.key\"\n \n # If proxying other services on this instance, add these:\n SSLProxyEngine On\n SSLProxyVerify none\n SSLProxyCheckPeerCN off\n\n # SSL from apache (deck) to gate on the local system is fine, \n # we don't really need to check gate's cert... but if we did, \n # we should look into SSLProxyCACertificateFile \n \n // ... rest of file omitted\n</VirtualHost>", "language": "xml", "name": "/etc/apache2/sites-available/spinnaker.conf" } ] } [/block] 3. Point Deck to the newly secured Gate endpoint. We'll use the preferred way of generating the `settings.js`, rather than editing it directly. In your `spinnaker-local.yml` file: [block:code] { "codes": [ { "code": "services:\n deck:\n gateUrl: https://localhost:8084", "language": "yaml", "name": "/opt/spinnaker/config/spinnaker-local.yml" } ] } [/block] 4. Regenerate the `settings.js` file. ``` sudo /opt/spinnaker/bin/reconfigure_spinnaker.sh ``` 5. Restart Deck ``` $ sudo service apache2 restart Apache needs to decrypt your SSL Keys for localhost:9000 (RSA) Please enter passphrase: ``` [block:api-header] { "type": "basic", "title": "Authenticating Users" } [/block] Authentication is proving that a user is who they say they are. Spinnaker has many other responsibilities, so we prefer to delegate this need to an external identity provider. This has the added advantage of not requiring users to create and remember yet another username/password combination. Spinnaker supports 3 forms of user authentication (some with authorization capabilities): * OAuth 2.0 * SAML 2.0 * X.509 Certificates # OAuth 2.0 Spinnaker includes OAuth 2.0 profile templates for Google (`googleOAuth`), Azure (`azureOAuth`), and GitHub (`githubOAuth`). This is achieved using Spring profiles, which we will configure and activate below. Any of the settings in the pre-configured profiles can be overridden in your `gate-<providerProfile>.yml` file. We will use the Google provider in this tutorial. ## OAuth Provider 1. Navigate to [https://console.developers.google.com/apis/credentials](https://console.developers.google.com/apis/credentials). 2. Create a new OAuth client ID. 3. Select "Web Application", and enter a name. 4. Under "Authorized redirect URIs", add `https://localhost:8084/login`, replacing "localhost" with your Gate hostname, if applicable. Click Create. 5. Note the generated client ID and client secret. Copy these to a safe place (like the config file you'll create in the next section). [block:image] { "images": [ { "image": [ "https://files.readme.io/tGGYpBY9Q0akkjGDlrwO_y22f2ycaxLR.png", "y22f2ycaxLR.png", "759", "761", "#3e5883", "" ], "caption": "Google OAuth Client ID creation screen." } ] } [/block] ## Gate Configuration 1. Create a `gate-googleOAuth.yml` file in `/opt/spinnaker/config/`. These values can be obtained by creating a new OAuth Client ID using the [Cloud Developers Console](https://console.developers.google.com/apis/credentials). [block:code] { "codes": [ { "code": "spring:\n oauth2:\n client:\n clientId: my-client-id\n clientSecret: ssshhh-its-a-sekret", "language": "yaml", "name": "/opt/spinnaker/config/gate-googleOAuth.yml" } ] } [/block] [block:callout] { "type": "info", "title": "Locking down access with UserInfoRequirements", "body": "You can add a `userInfoRequirements` section to enforce the `userInfo` response to contain certain fields. This can be used as a basic form of authorization. For example, you can lock down you Google Apps for Work account to just those users within your organization by adding the `hd` (for \"hosted domain\") parameter below:\n``` \nspring:\n oauth2:\n client:\n clientId: my-client-id\n clientSecret: ssshhh-its-a-sekret\n userInfoRequirements:\n hd: my-domain.net\n```" } [/block] 2. Activate the `googleOAuth` profile by adding an entry in `/etc/default/spinnaker` [block:code] { "codes": [ { "code": "# Spinnaker system defaults\n# ... file omitted\n\nGATE_OPTS=\"-Dspring.profiles.active=local,googleOAuth\"", "language": "shell", "name": "/etc/default/spinnaker" } ] } [/block] [block:callout] { "type": "warning", "body": "Be sure to include the `local` profile!" } [/block] ## Deck Configuration 1. Enable authentication in Deck via `spinnaker-local.yml`. It is also required to specify the user-accessible address of Deck [block:code] { "codes": [ { "code": "services:\n deck:\n baseUrl: https://localhost:9000\n auth:\n enabled: true", "language": "yaml", "name": "/opt/spinnaker/config/spinnaker-local.yml" } ] } [/block] 2. Generate the new `settings.js` file for Deck: ``` sudo /opt/spinnaker/bin/reconfigure_spinnaker.sh ``` 3. Restart Gate. 4. Navigate to Deck (generally `https://localhost:9000`). Notice the "Authenticating" screen that then redirects to the Google. [block:image] { "images": [ { "image": [ "https://files.readme.io/z8ARmUqhR7GfoC0FSSuT_auth.png", "auth.png", "434", "454", "#fbfbfb", "" ], "sizing": "smart", "border": false, "caption": "Deck beginning the authentication flow." } ] } [/block] [block:image] { "images": [ { "image": [ "https://files.readme.io/epetJEQmQgSpTh6O0r8U_glogin.png", "glogin.png", "319", "491", "#4c8bf2", "" ], "sizing": "smart", "caption": "Google account login for OAuth 2.0 Authentication." } ] } [/block] ## Bring-Your-Own OAuth Provider If you'd like to configure your own OAuth provider, you'll need to provide the following configuration values in your `gate-local.yml` file: [block:code] { "codes": [ { "code": "spring:\n oauth2:\n client:\n clientId:\n clientSecret:\n userAuthorizationUri: # Used to get an authorization code\n accessTokenUri: # Used to get an access token\n scope:\n resource:\n userInfoUri: # Used to the current user's profile\n userInfoMapping: # Used to map the userInfo response to our User\n email: \n firstName:\n lastName:\n username:", "language": "yaml", "name": "/opt/spinnaker/config/gate-local.yml" } ] } [/block] [block:callout] { "type": "warning", "title": "OAuth Provider Setup", "body": "Most OAuth provider's required a `redirect_uri` to be set during client ID creation. When required, the value should point to `https://localhost:8084/login`, or the hostname of your Gate instance instead of `localhost`." } [/block] # SAML 2.0 Spinnaker also provides SAML support. If you previously had SAML configured, please consult the [Gate SAML Authentication Migration Guide](doc:gate-saml-config). ## Identity Provider Configuration These are the general steps to take on your Identity Provider's Administrative console. 1. Download the `metadata.xml` file from the identity provider. It should look something like [block:code] { "codes": [ { "code": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<md:EntityDescriptor \n xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" \n entityID=\"https://accounts.google.com/o/saml2?idpid=SomeValueHere\" \n validUntil=\"2021-05-16T15:17:27.000Z\">\n <md:IDPSSODescriptor \n WantAuthnRequestsSigned=\"false\" \n protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:KeyDescriptor use=\"signing\">\n <ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n <ds:X509Data>\n <ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAVS/Sw5yMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ\nbmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv\nb2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTYwNTE3\nMTUxNzI3WhcNMjEwNTE2MTUxNzI3WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN\nTW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx\nCzAJBgNVBAYTblVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEF46OCAQ8A\nMIIBCgKCAQEA4JsnpS0ZBzb7DtlU7Zop7l+Kgr7NzusKWcEC6MOsFa4Dlt7jxv4ScKZ/61M5WKxd\n5YX0ol1rPokpNztj+Zk7OXrG8lDic0DpeDutc9pcq0+9/NYFF7WR7TDjh4B7Txnq7SerSB78fT8d\n4rK7Bd+cu/cBIyAAyZ5tLeLbmTnHAk093Y9vF3mdWQnfAhx4ldOfstF6G/d2ev7I5xjSKzQuH6Ew\n3bb3HLcM4uEVevOfNAlh1KoV4vQr+qzbc9UEFcPRwzuTwGa6QjfspWW7NgXKbHHC+X6a+gqJrke/\n6l2VvHaQBJ7oIyt4PCdel2cnUkvuxvzHPYedh1AgrIiSP1brSQIDAQABMA0GCSqGSI34DQEBCwUA\nA4IBAQCPqMAIau+pRDs2NZG1nGfyEMDfs0qop6FBa/wTNis75tLvay9MUlxXkTxm9aVxgggjEyc6\nXtDjpV0onrH0jBnSc+vRI1GFQ48EO3owy3uBIeR1aMy13ZwAA+KVizeoOrXBJbvIUZHo0yfKRzIu\ngtM58j58BdAFeYo+X9ds/ysvZ8FIGTLqMl/A3oO/yBNDjXR9Izoqgm7RX0JJXGL9Y1AgmEjxyqo9N\nMhxZAGxOHm9HZWWfVMcoe8p62mRJ2zf4lkNPBnDHrQ8MDPSsXewAuiSnRBDLxhdBgyThT/KW7Q06\nrGa6Dp0rntKWzZE3hGQS0AdsnuFY/OXbmkNG9WUrUg5x</ds:X509Certificate>\n </ds:X509Data>\n </ds:KeyInfo>\n </md:KeyDescriptor>\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>\n <md:SingleSignOnService \n Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" \n Location=\"https://accounts.google.com/o/saml2/idp?idpid=SomeValueHere\"/>\n <md:SingleSignOnService \n Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" \n Location=\"https://accounts.google.com/o/saml2/idp?idpid=SomeValueHere\"/>\n </md:IDPSSODescriptor>\n</md:EntityDescriptor>", "language": "xml" } ] } [/block] 2. Create a Spinnaker SAML application. 3. Specify the login URL as `https://localhost:8084/saml/SSO`. 4. Specify a unique entity ID (we'll use `io.spinnaker:test` in our example). 5. Enable the users you'd like to have access to your Spinnaker instance. ## Gate Configuration 1. Generate a keystore and key in a new Java Keystore with some password: ``` keytool -genkey -v -keystore saml.jks -alias saml -keyalg RSA -keysize 2048 -validity 10000 ``` 2. Create or modify your `gate-local.yml` file to include the following settings: [block:code] { "codes": [ { "code": "saml:\n enabled: true\n metadataUrl: file:/opt/spinnaker/config/metadata.xml\n keyStore: file:/opt/spinnaker/config/saml.jks\n keyStorePassword: hunter2\n keyStoreAliasName: saml\n issuerId: io.spinnaker:test\n redirectHostname: localhost:8084\n", "language": "yaml", "name": "/opt/spinnaker/config/gate-local.yml" } ] } [/block] [block:callout] { "type": "warning", "body": "Remove any OAuth 2.0 settings if present - OAuth and SAML are mutually exclusive authentication mechanisms and cannot coexist." } [/block] ## Deck Configuration 1. Enable authentication in Deck via `spinnaker-local.yml`. It is also required to specify the user-accessible address of Deck. [block:code] { "codes": [ { "code": "services:\n deck:\n baseUrl: https://localhost:9000\n auth:\n enabled: true", "language": "text", "name": "/opt/spinnaker/config/spinnaker-local.yml" } ] } [/block] 2. Generate the new `settings.js` file for Deck: ``` sudo /opt/spinnaker/bin/reconfigure_spinnaker.sh ``` 3. Restart Gate. 4. Navigate to Deck (generally `https://localhost:9000`). Notice the "Authenticating" screen that then redirects to your SAML Provider. [block:image] { "images": [ { "image": [ "https://files.readme.io/kZLq0mstSmCO1H8Nuc1y_auth.png", "auth.png", "434", "454", "#fbfbfb", "" ], "caption": "Deck beginning the authentication flow." } ] } [/block] [block:image] { "images": [ { "image": [ "https://files.readme.io/MIoEORFSHy0ZcBLXG9L1_oktaAuth.png", "oktaAuth.png", "663", "513", "#3d7798", "" ], "caption": "Sample SAML login using Okta as the Identity Provider." } ] } [/block] # LDAP Authentication Lightweight Directory Access Protocol (LDAP) is a standard way many organizations maintain user credentials and group memberships. Spinnaker uses the standard "bind" approach for user authentication. This is a fancy way of saying that Gate passes your username and password to the LDAP server, and if the connection is successful, you're considered authenticated. LDAP directories are generally organized by first defining a "root" distinguished name (DN). User DNs are constructed by combining this root DN with a user DN pattern. For example, if your root DN is `dc=my-organization,dc=com` and your user pattern is `uid={0},ou=users`, and user with the id `joe` would have a full, unique DN of `uid=joe,ou=users,dc=my-organization,dc=com`. When `joe` is trying to login, this full user DN is constructed and passed to the LDAP server with his password. The password is hashed on the server and compared to its own hashed version. If successful, the bind is successful and a session is established. We highly suggest the use of SSL for the LDAP connection (over `ldaps`). Otherwise, **user passwords are passed in clear text over the wire.** Here's a sample of the configuration that accomplishes the above example: ```yaml # /opt/spinnaker/config/gate-local.yml ldap: enabled: true url: ldaps://ldap.my-organization.com:10636/dc=my-organization,dc=com userDnPattern: uid={0},ou=users ``` It is also possible to use `ldap.userSearchBase` and `ldap.userSearchFilter` if the simpler `ldap.userDnPattern` does not match what your organization uses for `userDn`s. We won't explore this use case here, but you can read up more on LDAP search filters [here](https://confluence.atlassian.com/display/DEV/How+to+write+LDAP+search+filters). ## Authorization See the [Fiat setup](http://www.spinnaker.io/v1.0/docs/fiat-setup#section-ldap) documentation for establishing authorizations based on LDAP group membership. # Deck Configuration Just as with OAuth and SAML, you'll need to configure Deck to go through it's auth workflow. You can do this in `spinnaker-local` and the `/opt/spinnaker/bin/reconfigure_spinnaker.sh` script for VM images: ``` services: deck: auth: enabled: true ``` Or by setting `AUTH_ENABLED=true` when running Deck in a container environment, such as Kubernetes. # X.509 Client certificates are the last way to authenticate users. It is the preferred way to access Spinnaker's API endpoints via scripts. Remember that Certificate Authority (CA) we created in the [Part 1](#section-certificate-authority)? We'll use it to create a new client certificate. ## Client Certificate 1. Create the client key. Keep this file safe! ``` openssl genrsa -des3 -out client.key 4096 ``` 2. Generate a certificate signing request for the server. ``` openssl req -new -key client.key -out client.csr ``` 3. Use the CA to sign the server's request. If using an external CA, they will do this for you. ``` openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt ``` 4. Format the client certificate into browser importable form. ``` openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12 ``` ## Browser Configuration 1. In Google Chrome, navigate to [chrome://settings/certificates](chrome://settings/certificates), click the `Authorities` tab, and import the CA. [block:image] { "images": [ { "image": [ "https://files.readme.io/nV3yLHOmQUSybp2JJ41e_hys5KjMaXKe.png", "hys5KjMaXKe.png", "701", "491", "#de4315", "" ], "caption": "Import via the `Authorities` tab of chrome://settings/certificates" } ] } [/block] 2. Click the `Your Certificates` tab, and import your newly signed client certificate (`client.crt ## Gate Configuration Enable X.509 in `gate-local.yml` with the following `x509` tree as well as the new trustStore settings. Restart Gate after making these changes. [block:code] { "codes": [ { "code": "x509:\n enabled: true\n subjectPrincipalRegex: EMAILADDRESS=(.*?)(?:,|$) # optional\n \nserver:\n ssl:\n enabled: true\n keyStore: /opt/spinnaker/config/keystore.jks\n keyStorePassword: hunter2\n keyAlias: server\n trustStore: /opt/spinnaker/config/keystore.jks\n trustStorePassword: hunter2\n \ndefault:\n apiPort: 8083", "language": "yaml", "name": "/opt/spinnaker/config/gate-local.yml" } ] } [/block] [block:callout] { "type": "info", "body": "This works because we also imported the CA in the Keystore during our SSL setup alongside the server key." } [/block] ## Multiple Authentication Mechanisms X.509 can be enabled independent of OAuth and SAML. In this scenario, users hitting Deck will be prompted through the OAuth/SAML Identity Provider login screen, and API users (scripts) can simultaneously make calls with X.509 certificates. [block:api-header] { "type": "basic", "title": "Authorizing Users" } [/block] The current security model allows all authenticated users to see all resources (read access) within all accounts and applications. If enabled, specific accounts can be locked down to only allow users who are members of a certain group to perform mutating operations (write access). As of this writing, only groups from a SAML identity provider or Google Groups within your Google Apps for Work account are supported. ## Google Apps for Work (GAFW) A prerequisite for authorization using Google Groups is a Google Apps for Work account. You can [sign up here](https://apps.google.com) if your organization does not have one. ### Assign groups 1. In the [Admin Console](https://admin.google.com), open the left navigation and click "Groups." Here, you can add groups that specify various roles in Spinnaker. 2. Create a new group with the plus icon and give it a name. [block:callout] { "type": "warning", "body": "It is suggested you change the access level of this group/role to either \"Team\" or \"Restricted.\" Leaving the default (\"Public\") means any user in your organization can join this group without prior approval.", "title": "Access Level" } [/block] 3. Add users to your newly created group. ### Configure Domain-wide Delegation Gate uses a Google service account to access the Admin APIs on your behalf. We will create and configure this service account below. If you are running on Google Compute Engine (GCE) and already have a service account, you can reuse the same service account instead of creating a new one. 1. Follow the instructions to [create the service account and its JSON credentials](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#create_the_service_account_and_its_credentials). 2. Follow the instructions to [delegate Domain-wide access to the service account](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account). Your service account will need _ONLY_ the **https://www.googleapis.com/auth/admin.directory.group.readonly** scope. 3. Make sure you [enable the Admin SDK](https://console.cloud.google.com/apis/api/admin/overview). [block:callout] { "type": "info", "title": "Private Key", "body": "Ensure to download the service account key in JSON format. Transfer this file to your Spinnaker instance with the `gcloud copy-files` command." } [/block] ### Configure Gate Now that we have all the Google setup finished, we need to configure Spinnaker to enforce the roles we have set up. In your Spinnaker instance, add the following section to your `gate-local.yml` file: [block:code] { "codes": [ { "code": "auth:\n groupMembership:\n service: google\n google:\n credentialPath: /path/to/JSON.key\n adminUsername: admin@mydomain.com\n domain: mydomain.com", "language": "yaml", "name": "/opt/spinnaker/config/gate-local.yml" } ] } [/block] Once you have this configuration complete, restart Spinnaker and enjoy your enforced authN/Z roles! Each time you log into Spinnaker, you will be redirected to a Google sign-in page if you do not currently have a session. If you do have a session, Gate will enforce your required group memberships to access Spinnaker resources. ### Configure Clouddriver The last step is to ensure Clouddriver knows to restricted access for particular accounts to those users with access. If you specify multiple groups, a user only needs to be a member of *one* of the specified groups to be granted access. Create a `clouddriver-local.yml` file to override and enhance the `clouddriver.yml` file from the [spinnaker/spinnaker config](https://github.com/spinnaker/spinnaker/blob/master/config/clouddriver.yml). An example is shown below: [block:code] { "codes": [ { "code": "google:\n accounts:\n - name: my-protected-google-account\n project: my-spinnaker-project\n jsonPath: /opt/spinnaker/config/gafw-service-account.json\n requiredGroupMembership:\n - spinnaker-users", "language": "yaml", "name": "/opt/spinnaker/config/clouddriver-local.yml" } ] } [/block] ## Test Driving your changes Restart Gate and Clouddriver to have these changes take effect. If everything is configured correctly, mutating calls, such as "Terminate Instances" should return an error in the UI like below. [block:image] { "images": [ { "image": [ "https://files.readme.io/dGZa7AIaTtKGFXcRXCrY_authdenied%20(1).png", "authdenied (1).png", "724", "507", "#1e434e", "" ] } ] } [/block] [block:api-header] { "type": "basic", "title": "Configuring Session Timeout" } [/block] By default, a user's session will expire after 30 minutes of inactivity. You can change this duration by adding an entry in the `gate.yml` file: [block:code] { "codes": [ { "code": "server:\n session:\n timeoutInSeconds: 3600", "language": "yaml", "name": "/opt/spinnaker/config/gate-local.yml" } ] } [/block] [block:api-header] { "type": "basic", "title": "Troubleshooting" } [/block] ### Something's not right. How do I turn on debug logs? Add the following to your configuration to turn on logging. [block:code] { "codes": [ { "code": "logging:\n level:\n com.netflix.spinnaker.gate.security: DEBUG\n org.springframework.security: DEBUG\n org.springframework.web: DEBUG", "language": "yaml", "name": "gate-local.yml" } ] } [/block] ### I see Deck requesting `/auth/info` and hanging. What gives? This is the old endpoint. You'll need to update your `settings.js` file. There are two ways to do this. 1. Edit your `/opt/spinnaker/config/settings.js` file and change the `authEndpoint` to: [block:code] { "codes": [ { "code": "window.spinnakerSettings = {\n // ...\n authEndpoint: gateUrl + '/auth/user',\n //...\n}", "language": "javascript" } ] } [/block] and run this to regenerate the `/opt/deck/html/settings.js` file: ``` sudo /opt/spinnaker/bin/reconfigure_spinnaker.sh ``` 2. Update your `AUTH_ENDPOINT` environment var to end with `/auth/user`. This is likely applicable if you're using Docker Compose or a development instance. --- ### I'm getting an `Error: redirect_uri_mismatch` from my OAuth provider. The full error may look something like: ``` Error: redirect_uri_mismatch. The redirect URI in the request, https://<some_url>/login, does not match the ones authorized for the OAuth client. ``` This likely means you've not set up your OAuth credentials correctly. Ensure that the Authorized Request URIs list contains "http**s**://my-gate-address/login" (no trailing /). In the case that this is set correctly, see the next answer. --- ### I terminate SSL connections outside of Gate, and my `redirect_uri` configuration is correct (see above). How do I make this work? By default, Gate will use its own protocol, host, and port when constructing the `redirect_uri`. In the case where you want to terminate SSL connections outside of Gate, you'll need to override this by providing the exact address in your gate-<provider>.yml: [block:code] { "codes": [ { "code": "spring:\n oauth2:\n client:\n preEstablishedRedirectUri: https://my-real-gate-address.com/login\n useCurrentUri: false", "language": "yaml", "name": "/opt/spinnaker/config/gate-<provider>.yml" } ] } [/block] --- ### I'm able to login to my identity provider, but afterwards I get a `Requested redirect address not recognized` error In your `spinnaker-local.yml` file, you need to set `services.deck.baseUrl` to the address of Deck. We only allow redirects to URLs that match the protocol, host, and port of the `services.deck.baseUrl`. For example: [block:code] { "codes": [ { "code": "services:\n deck:\n baseUrl: https://localhost:9000", "language": "yaml", "name": "/opt/spinnaker/config/spinnaker-local.yml" } ] } [/block] ### I have deck behind an external load balancer. During authentication, it redirects to port :9000 instead of the port the LB is listening on. When reverse proxying, Apache will try to determine your host/port from the incoming request. If the port isn't specified, it will write in the port it's listening on (which for deck is 9000 by default). In your apache configuration file, add the following section. [block:code] { "codes": [ { "code": "<VirtualHost *:9000>\n # ServerName https://my_deck_hostname:externally_visible_port\n # For instance, if your LB is listening on \n # https://spinnaker.mydomain.com:443 and forwarding that to 9000, you want:\n ServerName https://spinnaker.mydomain.com:443\n UseCanonicalName On\n \n ... rest of file omitted\n</VirtualHost>", "language": "xml", "name": "/etc/apache2/sites-available/spinnaker.conf" } ] } [/block] By turning this setting on, you force Apache to rewrite the URL using the ServerName instead of trying to infer it from the request. Alternately, [this issue](https://github.com/spinnaker/spinnaker/issues/997) may be of help.