From 1435445b7d33c69f19213bfedbcc48810718b368 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Sun, 28 May 2017 00:55:20 -0700 Subject: [PATCH 01/12] initial commit iap samples --- appengine/iap/README.md | 27 +++ appengine/iap/pom.xml | 57 ++++++ .../com/example/appengine/iap/JwtServlet.java | 34 ++++ .../src/main/webapp/WEB-INF/appengine-web.xml | 18 ++ appengine/iap/src/main/webapp/WEB-INF/web.xml | 14 ++ iap/README.md | 39 +++++ iap/pom.xml | 72 ++++++++ .../java/com/example/iap/BuildIapRequest.java | 135 +++++++++++++++ .../example/iap/VerifyIapRequestHeader.java | 162 ++++++++++++++++++ 9 files changed, 558 insertions(+) create mode 100644 appengine/iap/README.md create mode 100644 appengine/iap/pom.xml create mode 100644 appengine/iap/src/main/java/com/example/appengine/iap/JwtServlet.java create mode 100644 appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml create mode 100644 appengine/iap/src/main/webapp/WEB-INF/web.xml create mode 100644 iap/README.md create mode 100644 iap/pom.xml create mode 100644 iap/src/main/java/com/example/iap/BuildIapRequest.java create mode 100644 iap/src/main/java/com/example/iap/VerifyIapRequestHeader.java diff --git a/appengine/iap/README.md b/appengine/iap/README.md new file mode 100644 index 00000000000..ffa15659128 --- /dev/null +++ b/appengine/iap/README.md @@ -0,0 +1,27 @@ +# Identity-Aware Proxy sample for Google App Engine + +This sample demonstrates how to use the [Identity-Aware Proxy][iap-docs] on [Google App +Engine][ae-docs]. + +[iap-docs]: https://cloud.google.com/iap/docs/ +[ae-docs]: https://cloud.google.com/appengine/docs/java/ + +## Running locally + +This application depends on being enabled behind an IAP, so this program should not be run locally. + +## Deploying + +- Update [appengine-web.xml](src/main/test/app/src/main/webapp/WEB-INF/appengine-web.xml) with your project-id +- Deploy the application to the project + ``` + mvn clean appengine:update + ``` +- [Enable](https://cloud.google.com/iap/docs/app-engine-quickstart) Identity-Aware Proxy on the App Engine app. +- Add the email account you'll be running the test as to the Identity-Aware Proxy access list for the project. + +## Test + +Once deployed, access `https://your-project-id.appspot.com` . This should now prompt you to sign in for access. +Sign in with the email account that was added to the Identity-Aware proxy access list. +You should now see the jwt token that was received from the IAP server. diff --git a/appengine/iap/pom.xml b/appengine/iap/pom.xml new file mode 100644 index 00000000000..8afb4306b44 --- /dev/null +++ b/appengine/iap/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + iap-demo + + + com.google.cloud + appengine-doc-samples + 1.0.0 + .. + + + + javax.servlet + servlet-api + 2.5 + provided + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + org.apache.maven.plugins + 3.3 + maven-compiler-plugin + + 1.7 + 1.7 + + + + com.google.appengine + appengine-maven-plugin + ${appengine.sdk.version} + + + + diff --git a/appengine/iap/src/main/java/com/example/appengine/iap/JwtServlet.java b/appengine/iap/src/main/java/com/example/appengine/iap/JwtServlet.java new file mode 100644 index 00000000000..b6f7d95584f --- /dev/null +++ b/appengine/iap/src/main/java/com/example/appengine/iap/JwtServlet.java @@ -0,0 +1,34 @@ +/** + * Copyright 2017 Google Inc. + * + *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + *

http://www.apache.org/licenses/LICENSE-2.0 + * + *

Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.appengine.iap; + +import java.io.IOException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Identity Aware Proxy (IAP) Test application to reflect jwt token issued by IAP. IAP must be + * enabled on application. {@see https://cloud.google.com/iap/docs/app-engine-quickstart} + */ +@SuppressWarnings("serial") +public class JwtServlet extends HttpServlet { + + private static final String IAP_JWT_HEADER = "x-goog-authenticated-user-jwt"; + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + resp.getWriter().print(IAP_JWT_HEADER + ":" + req.getHeader(IAP_JWT_HEADER)); + } +} diff --git a/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..a48cd61cf83 --- /dev/null +++ b/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,18 @@ + + + + ja-test-iap-2 + alpha-001 + true + diff --git a/appengine/iap/src/main/webapp/WEB-INF/web.xml b/appengine/iap/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..b4a68e55196 --- /dev/null +++ b/appengine/iap/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,14 @@ + + + + hello + com.example.appengine.iap.JwtServlet + + + hello + / + + diff --git a/iap/README.md b/iap/README.md new file mode 100644 index 00000000000..2804b3c7173 --- /dev/null +++ b/iap/README.md @@ -0,0 +1,39 @@ +# Cloud Identity-Aware Proxy Java Samples +Cloud Identity-Aware Proxy (Cloud IAP) lets you manage access to applications running in Compute Engine, App Engine standard environment, and Container Engine. Cloud IAP establishes a central authorization layer for applications accessed by HTTPS, enabling you to adopt an application-level access control model instead of relying on network-level firewalls. When you enable Cloud IAP, you must also use signed headers or the App Engine standard environment Users API to secure your app. +## Setup +- A Google Cloud project with billing enabled +- [Create an App engine service account](https://cloud.google.com/docs/authentication#getting_credentials_for_server-centric_flow) and download the credentials file as JSON. +- Install the [Google Cloud SDK](https://cloud.google.com/sdk/) and run: +``` + gcloud init + gcloud app create +``` + +## Description + +- [BuildIapRequest.java](src/main/java/com/example/iap/BuildIapRequest.java) demonstrates how to set the +`Authorization : Bearer` header to authorize access to an IAP protected URL. +- [VerifyIapRequestHeader.java](src/main/java/com/example/iap/VerifyIapRequestHeader.java) demonstrates how to +verify the JWT token in an incoming request to an IAP protected resource. + +## Testing +- Deploy the [demo app engine application](../appengine/iap/README.md). This application will return the JWT token to an authorized incoming request. +It will be used to test both the authorization of an incoming request to an IAP protected resource and the JWT token returned from IAP. + - Update [appengine-web.xml](../appengine/src/main/webapp/WEB-INF/appengine-web.xml) + with your project-id + - Deploy the application to the project + ``` + mvn clean appengine:update + ``` + - [Enable](https://cloud.google.com/iap/docs/app-engine-quickstart) Identity-Aware Proxy on the App Engine app. +- Set the environment variable `GOOGLE_APPLICATION_CREDENTIALS` to point to the service account credentials file +- Add the service account email you'll be running the test as to the Identity-Aware Proxy access list for the project. +- Set the environment variable `IAP_PROTECTED_URL` to point to `https://your-project-id.appspot.com` +- Run the integration test: +``` + mvn -Dtest=com.example.iap.BuildAndVerifyIapRequestIT verify +``` + +## References +[JWT library for Java](https://github.com/auth0/java-jwt) +[Cloud IAP docs](https://cloud.google.com/iap/docs/) diff --git a/iap/pom.xml b/iap/pom.xml new file mode 100644 index 00000000000..6767d64a7f1 --- /dev/null +++ b/iap/pom.xml @@ -0,0 +1,72 @@ + + + + + + 4.0.0 + jar + + com.example + iap-samples + 1.0-SNAPSHOT + + + 1.8 + 1.8 + UTF-8 + 9.4.3.v20170317 + + + + + + com.fasterxml.jackson.core + jackson-core + 2.8.6 + + + + + + + javax.servlet + javax.servlet-api + 3.1.0 + + + + + com.google.auth + google-auth-library-oauth2-http + 0.6.0 + + + com.auth0 + java-jwt + 3.2.0 + + + + + + junit + junit + 4.12 + + + + diff --git a/iap/src/main/java/com/example/iap/BuildIapRequest.java b/iap/src/main/java/com/example/iap/BuildIapRequest.java new file mode 100644 index 00000000000..c5467f37d62 --- /dev/null +++ b/iap/src/main/java/com/example/iap/BuildIapRequest.java @@ -0,0 +1,135 @@ +/** + * Copyright 2017 Google Inc. + * + *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + *

http://www.apache.org/licenses/LICENSE-2.0 + * + *

Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.iap; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.UrlEncodedContent; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.GenericData; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; +import java.io.IOException; +import java.net.URL; +import java.security.interfaces.RSAPrivateKey; +import java.time.Clock; +import java.time.Instant; +import java.util.Collections; +import java.util.Date; + +public class BuildIapRequest { + // [START generate_iap_request] + private static final String IAM_SCOPE = "https://www.googleapis.com/auth/iam"; + private static final String OAUTH_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token"; + private static final String JWT_BEARER_TOKEN_GRANT_TYPE = + "urn:ietf:params:oauth:grant-type:jwt-bearer"; + private static final long EXPIRATION_TIME_IN_SECONDS = 3600L; + + private static final HttpTransport httpTransport = new NetHttpTransport(); + + private static Clock clock = Clock.systemUTC(); + + private BuildIapRequest() {} + + private static String getBaseUrl(URL url) throws Exception { + String urlFilePath = url.getFile(); + int pathDelim = urlFilePath.lastIndexOf('/'); + String path = (pathDelim > 0) ? urlFilePath.substring(0, pathDelim) : ""; + return (url.getProtocol() + "://" + url.getHost() + path).trim(); + } + + private static ServiceAccountCredentials getCredentials() throws Exception { + GoogleCredentials credentials = + GoogleCredentials.getApplicationDefault().createScoped(Collections.singleton(IAM_SCOPE)); + // service account credentials are required to sign the jwt token + if (credentials == null || !(credentials instanceof ServiceAccountCredentials)) { + throw new Exception("Google credentials : service accounts credentials expected"); + } + return (ServiceAccountCredentials) credentials; + } + + private static String getSignedJWToken(ServiceAccountCredentials credentials, String baseUrl) + throws IOException { + Instant now = Instant.now(clock); + long expirationTime = now.getEpochSecond() + EXPIRATION_TIME_IN_SECONDS; + // generate jwt signed by service account + return JWT.create() + .withKeyId(credentials.getPrivateKeyId()) + .withAudience(OAUTH_TOKEN_URI) + .withIssuer(credentials.getClientEmail()) + .withSubject(credentials.getClientEmail()) + .withIssuedAt(Date.from(now)) + .withExpiresAt(Date.from(Instant.ofEpochSecond(expirationTime))) + .withClaim("target_audience", baseUrl) + .sign(Algorithm.RSA256(null, (RSAPrivateKey) credentials.getPrivateKey())); + } + + private static String getGoogleIdToken(String jwt) throws Exception { + final GenericData tokenRequest = + new GenericData().set("grant_type", JWT_BEARER_TOKEN_GRANT_TYPE).set("assertion", jwt); + final UrlEncodedContent content = new UrlEncodedContent(tokenRequest); + + final HttpRequestFactory requestFactory = httpTransport.createRequestFactory(); + + final HttpRequest request = + requestFactory + .buildPostRequest(new GenericUrl(OAUTH_TOKEN_URI), content) + .setParser(new JsonObjectParser(JacksonFactory.getDefaultInstance())); + + HttpResponse response; + String idToken = null; + response = request.execute(); + GenericData responseData = response.parseAs(GenericData.class); + idToken = (String) responseData.get("id_token"); + return idToken; + } + + public static HttpRequest buildIAPRequest(HttpRequest request) throws Exception { + // get service account credentials + ServiceAccountCredentials credentials = getCredentials(); + // get the base url of the request URL + String baseUrl = getBaseUrl(request.getUrl().toURL()); + String jwt = getSignedJWToken(credentials, baseUrl); + if (jwt == null) { + throw new Exception( + "Unable to create a signed jwt token for : " + + baseUrl + + "with issuer : " + + credentials.getClientEmail()); + } + + String idToken = getGoogleIdToken(jwt); + if (idToken == null) { + throw new Exception("Unable to retrieve open id token"); + } + + // Create an authorization header with bearer token + HttpHeaders httpHeaders = request.getHeaders().clone().setAuthorization("Bearer " + idToken); + + // create request with jwt authorization header + return httpTransport + .createRequestFactory() + .buildRequest(request.getRequestMethod(), request.getUrl(), request.getContent()) + .setHeaders(httpHeaders); + } + // [END generate_iap_request] +} diff --git a/iap/src/main/java/com/example/iap/VerifyIapRequestHeader.java b/iap/src/main/java/com/example/iap/VerifyIapRequestHeader.java new file mode 100644 index 00000000000..d8ac0a4e6e9 --- /dev/null +++ b/iap/src/main/java/com/example/iap/VerifyIapRequestHeader.java @@ -0,0 +1,162 @@ +/** + * Copyright 2017 Google Inc. + * + *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + *

http://www.apache.org/licenses/LICENSE-2.0 + * + *

Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.iap; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.ECDSAKeyProvider; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpStatusCodes; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.util.PemReader; +import com.google.api.client.util.PemReader.Section; +import java.io.IOException; +import java.io.StringReader; +import java.net.URL; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +/** Verify IAP authorization JWT token in incoming request. */ +public class VerifyIapRequestHeader { + + private static final String PUBLIC_KEY_VERIFICATION_URL = + "https://www.gstatic.com/iap/verify/public_key"; + private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap"; + + private final Map keyCache = new HashMap<>(); + private final ObjectMapper mapper = new ObjectMapper(); + private final TypeReference> typeRef = + new TypeReference>() {}; + + private ECDSAKeyProvider keyProvider = + new ECDSAKeyProvider() { + @Override + public ECPublicKey getPublicKeyById(String kid) { + ECPublicKey key = keyCache.get(kid); + if (key != null) { + return key; + } + try { + HttpRequest request = + new NetHttpTransport() + .createRequestFactory() + .buildGetRequest(new GenericUrl(PUBLIC_KEY_VERIFICATION_URL)); + HttpResponse response = request.execute(); + if (response.getStatusCode() != HttpStatusCodes.STATUS_CODE_OK) { + return null; + } + Map keys = mapper.readValue(response.parseAsString(), typeRef); + for (Map.Entry keyData : keys.entrySet()) { + if (!keyData.getKey().equals(kid)) { + continue; + } + key = getKey(keyData.getValue()); + if (key != null) { + keyCache.putIfAbsent(kid, key); + } + } + + } catch (IOException e) { + // ignore exception + } + + return key; + } + + @Override + public ECPrivateKey getPrivateKey() { + // ignore : only required for signing requests + return null; + } + + @Override + public String getPrivateKeyId() { + // ignore : only required for signing requests + return null; + } + }; + + private static String getBaseUrl(URL url) throws Exception { + String urlFilePath = url.getFile(); + int pathDelim = urlFilePath.lastIndexOf('/'); + String path = (pathDelim > 0) ? urlFilePath.substring(0, pathDelim) : ""; + return (url.getProtocol() + "://" + url.getHost() + path).trim(); + } + + DecodedJWT verifyJWTToken(HttpRequest request) throws Exception { + // Check for iap jwt header in incoming request + String jwtToken = + request.getHeaders().getFirstHeaderStringValue("x-goog-authenticated-user-jwt"); + if (jwtToken == null) { + return null; + } + String baseUrl = getBaseUrl(request.getUrl().toURL()); + return verifyJWTToken(jwtToken, baseUrl); + } + + DecodedJWT verifyJWTToken(String jwtToken, String baseUrl) throws Exception { + Algorithm algorithm = Algorithm.ECDSA256(keyProvider); + + // Time constraints are automatically checked, use acceptLeeway to specify a leeway window + // The token was issued in a past date "iat" < TODAY + // The token hasn't expired yet "exp" > TODAY + JWTVerifier verifier = + JWT.require(algorithm).withAudience(baseUrl).withIssuer(IAP_ISSUER_URL).build(); + + DecodedJWT decodedJWT = verifier.verify(jwtToken); + + if (decodedJWT.getSubject() == null) { + throw new JWTVerificationException("Subject expected, not found"); + } + if (decodedJWT.getClaim("email") == null) { + throw new JWTVerificationException("Email expected, not found"); + } + return decodedJWT; + } + + private ECPublicKey getKey(String keyText) throws IOException { + StringReader reader = new StringReader(keyText); + Section section = PemReader.readFirstSectionAndClose(reader, "PUBLIC KEY"); + if (section == null) { + throw new IOException("Invalid data."); + } else { + byte[] bytes = section.getBase64DecodedBytes(); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes); + try { + KeyFactory kf = KeyFactory.getInstance("EC"); + PublicKey publicKey = kf.generatePublic(keySpec); + if (publicKey instanceof ECPublicKey) { + return (ECPublicKey) publicKey; + } + } catch (InvalidKeySpecException | NoSuchAlgorithmException var7) { + throw new IOException("Unexpected exception reading data", var7); + } + } + return null; + } +} From ee46851b47a46bd0ac72f69ce0d98c15a13a6945 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Sun, 28 May 2017 00:55:55 -0700 Subject: [PATCH 02/12] adding test --- .../iap/BuildAndVerifyIapRequestIT.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 iap/src/test/java/com/example/iap/BuildAndVerifyIapRequestIT.java diff --git a/iap/src/test/java/com/example/iap/BuildAndVerifyIapRequestIT.java b/iap/src/test/java/com/example/iap/BuildAndVerifyIapRequestIT.java new file mode 100644 index 00000000000..3638599a5dc --- /dev/null +++ b/iap/src/test/java/com/example/iap/BuildAndVerifyIapRequestIT.java @@ -0,0 +1,76 @@ +/** + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.iap; + +import static com.example.iap.BuildIapRequest.buildIAPRequest; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import com.auth0.jwt.interfaces.DecodedJWT; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import org.apache.http.HttpStatus; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BuildAndVerifyIapRequestIT { + + private String iapProtectedUrl = System.getenv("IAP_PROTECTED_URL"); + private HttpTransport httpTransport = new NetHttpTransport(); + private VerifyIapRequestHeader verifyIapRequestHeader = new VerifyIapRequestHeader(); + + @Before + public void setUp() { + assertNotNull(iapProtectedUrl); + } + + // Access an IAP protected url without signed jwt authorization header + @Test + public void accessIapProtectedResourceFailsWithoutJwtHeader() throws Exception { + HttpRequest request = + httpTransport.createRequestFactory().buildGetRequest(new GenericUrl(iapProtectedUrl)); + try { + request.execute(); + } catch (HttpResponseException e) { + assertEquals(e.getStatusCode(), HttpStatus.SC_UNAUTHORIZED); + } + } + + // Access an IAP protected url with a signed jwt authorization header, verify jwt token + @Test + public void testGenerateAndVerifyIapRequestIsSuccessful() throws Exception { + HttpRequest request = + httpTransport.createRequestFactory().buildGetRequest(new GenericUrl(iapProtectedUrl)); + HttpRequest iapRequest = buildIAPRequest(request); + HttpResponse response = iapRequest.execute(); + assertEquals(response.getStatusCode(), HttpStatus.SC_OK); + String headerWithtoken = response.parseAsString(); + String[] split = headerWithtoken.split(":"); + assertNotNull(split); + assertEquals(split.length, 2); + assertEquals(split[0].trim(), "x-goog-authenticated-user-jwt"); + DecodedJWT decodedJWT = verifyIapRequestHeader.verifyJWTToken(split[1].trim(), iapProtectedUrl); + assertNotNull(decodedJWT); + } +} From b491208d555c0b76f085fa22f9e0907d53e6c977 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Sun, 28 May 2017 00:58:36 -0700 Subject: [PATCH 03/12] cleanup --- appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml index a48cd61cf83..eec674e0766 100644 --- a/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml +++ b/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml @@ -12,7 +12,7 @@ limitations under the License. --> - ja-test-iap-2 + YOUR-PROJECT-ID alpha-001 true From dca4a022514189300439eb024290cdced7f34012 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Sun, 28 May 2017 01:01:31 -0700 Subject: [PATCH 04/12] license update --- appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml index eec674e0766..be18ac6ce52 100644 --- a/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml +++ b/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml @@ -1,6 +1,6 @@ + appengine-iap + + + 1.9.53 + + com.google.cloud appengine-doc-samples diff --git a/appengine/pom.xml b/appengine/pom.xml index 151a4d840d9..2950bd7651c 100644 --- a/appengine/pom.xml +++ b/appengine/pom.xml @@ -61,6 +61,7 @@ guestbook-objectify helloworld helloworld-new-plugins + iap images logs mailgun diff --git a/iap/pom.xml b/iap/pom.xml index 2fa49738965..4f01bf5fdde 100644 --- a/iap/pom.xml +++ b/iap/pom.xml @@ -15,20 +15,17 @@ limitations under the License. --> - - - 4.0.0 - jar - + + 4.0.0 + jar com.example - iap-samples - 1.0-SNAPSHOT + iap-samples + 1.0-SNAPSHOT - 1.8 - 1.8 + 1.8 + 1.8 UTF-8 - 9.4.3.v20170317 From 7a13ebc75f70d3a8113f1e4947a0d04b5da8f242 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Tue, 30 May 2017 11:35:05 -0700 Subject: [PATCH 08/12] using new tooling readme, pom.xml cleanup --- appengine/iap/README.md | 14 ++++++++++++-- appengine/iap/pom.xml | 8 ++------ .../src/main/webapp/WEB-INF/appengine-web.xml | 1 - iap/README.md | 19 +++++++++++-------- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/appengine/iap/README.md b/appengine/iap/README.md index 5f1b0c5e84c..39ca52ea4f7 100644 --- a/appengine/iap/README.md +++ b/appengine/iap/README.md @@ -6,16 +6,26 @@ Engine][ae-docs]. [iap-docs]: https://cloud.google.com/iap/docs/ [ae-docs]: https://cloud.google.com/appengine/docs/java/ +## Setup + +Install the [Google Cloud SDK](https://cloud.google.com/sdk/) and run: +``` + gcloud init +``` +If this is your first time creating an App engine application: +``` + gcloud app create +``` + ## Running locally This application depends on being enabled behind an IAP, so this program should not be run locally. ## Deploying -- Update [appengine-web.xml](src/main/test/app/src/main/webapp/WEB-INF/appengine-web.xml) with your project-id - Deploy the application to the project ``` - mvn clean appengine:update + mvn clean appengine:deploy ``` - [Enable](https://cloud.google.com/iap/docs/app-engine-quickstart) Identity-Aware Proxy on the App Engine app. - Add the email account you'll be running the test as to the Identity-Aware Proxy access list for the project. diff --git a/appengine/iap/pom.xml b/appengine/iap/pom.xml index 0f8b1bb321f..15b6cd930d8 100644 --- a/appengine/iap/pom.xml +++ b/appengine/iap/pom.xml @@ -20,10 +20,6 @@ Copyright 2017 Google Inc. com.example.appengine appengine-iap - - 1.9.53 - - com.google.cloud appengine-doc-samples @@ -52,9 +48,9 @@ Copyright 2017 Google Inc. - com.google.appengine + com.google.cloud.tools appengine-maven-plugin - ${appengine.sdk.version} + 1.3.1 diff --git a/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml index be18ac6ce52..153fcd0c49f 100644 --- a/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml +++ b/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml @@ -12,7 +12,6 @@ limitations under the License. --> - YOUR-PROJECT-ID alpha-001 true diff --git a/iap/README.md b/iap/README.md index 0c8192b8fdd..d8222e2c512 100644 --- a/iap/README.md +++ b/iap/README.md @@ -3,31 +3,33 @@ Cloud Identity-Aware Proxy (Cloud IAP) lets you manage access to applications ru ## Setup - A Google Cloud project with billing enabled -- [Create an App engine service account](https://cloud.google.com/docs/authentication#getting_credentials_for_server-centric_flow) and download the credentials file as JSON. +- A service account with private key credentials is required to create signed bearer tokens. + - [Create an App engine service account](https://cloud.google.com/docs/authentication#getting_credentials_for_server-centric_flow) and download the credentials file as JSON. + - Set the environment variable `GOOGLE_APPLICATION_CREDENTIALS` to point to the service account credentials file. - Install the [Google Cloud SDK](https://cloud.google.com/sdk/) and run: ``` gcloud init +``` + If this is your first time creating an App engine application: +``` gcloud app create ``` ## Description - [BuildIapRequest.java](src/main/java/com/example/iap/BuildIapRequest.java) demonstrates how to set the -`Authorization : Bearer` header to authorize access to an IAP protected URL. +`Authorization : Bearer` header with a signed JWT token to authorize access to an IAP protected URL. - [VerifyIapRequestHeader.java](src/main/java/com/example/iap/VerifyIapRequestHeader.java) demonstrates how to verify the JWT token in an incoming request to an IAP protected resource. ## Testing - Deploy the [demo app engine application](../appengine/iap/README.md). This application will return the JWT token to an authorized incoming request. It will be used to test both the authorization of an incoming request to an IAP protected resource and the JWT token returned from IAP. - - Update [appengine-web.xml](../appengine/src/main/webapp/WEB-INF/appengine-web.xml) - with your project-id - Deploy the application to the project ``` - mvn clean appengine:update + mvn clean appengine:deploy ``` - - [Enable](https://cloud.google.com/iap/docs/app-engine-quickstart) Identity-Aware Proxy on the App Engine app. -- Set the environment variable `GOOGLE_APPLICATION_CREDENTIALS` to point to the service account credentials file -- Add the service account email you'll be running the test as to the Identity-Aware Proxy access list for the project. +- [Enable](https://cloud.google.com/iap/docs/app-engine-quickstart) Identity-Aware Proxy on the App Engine app. +- Add the service account email to the Identity-Aware Proxy access list for the project. - Set the environment variable `IAP_PROTECTED_URL` to point to `https://your-project-id.appspot.com` - Run the integration test: ``` @@ -37,3 +39,4 @@ It will be used to test both the authorization of an incoming request to an IAP ## References [JWT library for Java](https://github.com/auth0/java-jwt) [Cloud IAP docs](https://cloud.google.com/iap/docs/) +[Service account credentials](https://cloud.google.com/docs/authentication#getting_credentials_for_server-centric_flow) From ebd29dccf01e666cf4508f92d06eac3f239b2832 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Tue, 30 May 2017 12:07:51 -0700 Subject: [PATCH 09/12] adding doc tags --- iap/src/main/java/com/example/iap/VerifyIapRequestHeader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iap/src/main/java/com/example/iap/VerifyIapRequestHeader.java b/iap/src/main/java/com/example/iap/VerifyIapRequestHeader.java index d8ac0a4e6e9..9e21fa79547 100644 --- a/iap/src/main/java/com/example/iap/VerifyIapRequestHeader.java +++ b/iap/src/main/java/com/example/iap/VerifyIapRequestHeader.java @@ -43,7 +43,7 @@ /** Verify IAP authorization JWT token in incoming request. */ public class VerifyIapRequestHeader { - + // [START verify_iap_request] private static final String PUBLIC_KEY_VERIFICATION_URL = "https://www.gstatic.com/iap/verify/public_key"; private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap"; @@ -159,4 +159,5 @@ private ECPublicKey getKey(String keyText) throws IOException { } return null; } + // [END verify_iap_request] } From 81371379167f48d350944f6ae7a49a5a60e0cb95 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Tue, 30 May 2017 12:54:44 -0700 Subject: [PATCH 10/12] remove app engine deployment info from iap/README.md --- iap/README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/iap/README.md b/iap/README.md index d8222e2c512..75238df3966 100644 --- a/iap/README.md +++ b/iap/README.md @@ -10,10 +10,6 @@ Cloud Identity-Aware Proxy (Cloud IAP) lets you manage access to applications ru ``` gcloud init ``` - If this is your first time creating an App engine application: -``` - gcloud app create -``` ## Description - [BuildIapRequest.java](src/main/java/com/example/iap/BuildIapRequest.java) demonstrates how to set the @@ -24,10 +20,6 @@ verify the JWT token in an incoming request to an IAP protected resource. ## Testing - Deploy the [demo app engine application](../appengine/iap/README.md). This application will return the JWT token to an authorized incoming request. It will be used to test both the authorization of an incoming request to an IAP protected resource and the JWT token returned from IAP. - - Deploy the application to the project - ``` - mvn clean appengine:deploy - ``` - [Enable](https://cloud.google.com/iap/docs/app-engine-quickstart) Identity-Aware Proxy on the App Engine app. - Add the service account email to the Identity-Aware Proxy access list for the project. - Set the environment variable `IAP_PROTECTED_URL` to point to `https://your-project-id.appspot.com` From 3ce439359ab1f52be1d266749ae9c0e13fbe6a35 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Tue, 30 May 2017 12:58:23 -0700 Subject: [PATCH 11/12] removing version from appengine-web.xml --- appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml index 153fcd0c49f..b30cd159e63 100644 --- a/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml +++ b/appengine/iap/src/main/webapp/WEB-INF/appengine-web.xml @@ -12,6 +12,5 @@ limitations under the License. --> - alpha-001 true From 7ea47196301248468920c00659326a296e0ceb39 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Tue, 30 May 2017 13:10:25 -0700 Subject: [PATCH 12/12] adding iap to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 61281c1f694..f0920bdd291 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Technology Samples: * [Bigquery](bigquery) * [Datastore](datastore) * [Endpoints](endpoints) +* [Identity-Aware Proxy](iap) * [Key Management Service](kms) * [Logging](logging) * [Monitoring](monitoring)