Skip to content

Commit 509d5e4

Browse files
jprzychodzenlesv
authored andcommitted
Add signed-metadata example (#828)
* https://cloud.google.com/compute/docs/instances/verifying-instance-identity requires Java example
1 parent 3096f5a commit 509d5e4

File tree

11 files changed

+544
-0
lines changed

11 files changed

+544
-0
lines changed

compute/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<module>error-reporting</module>
3535
<module>mailjet</module>
3636
<module>sendgrid</module>
37+
<module>signed-metadata</module>
3738
</modules>
3839

3940
</project>

compute/signed-metadata/README.md

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Verify instance identity Java sample for Google Compute Engine
2+
3+
This repository contains example code in Java that downloads and verifies JWT
4+
provided by metadata endpoint, which is available on Compute Engine instances in
5+
GCP.
6+
7+
More about this verification can be read on official Google Cloud documentation
8+
[Veryfying the Identity of
9+
Instances](https://cloud.google.com/compute/docs/instances/verifying-instance-identity).
10+
11+
## Running on Compute Engine
12+
13+
To run the sample, you will need to do the following:
14+
15+
1. Create a compute instance on the Google Cloud Platform Developer's Console
16+
1. SSH into the instance you created
17+
1. Update packages and install required packages
18+
19+
`sudo apt-get update && sudo apt-get install git-core openjdk-8-jdk maven`
20+
21+
1. Clone the repo
22+
23+
`git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git`
24+
25+
1. Navigate to `java-docs-samples/compute/signed-metadata/`
26+
27+
`cd java-docs-samples/compute/signed-metadata/`
28+
29+
1. Use maven to package the class as a jar
30+
31+
`mvn clean package`
32+
33+
1. Make sure that openjdk 8 is the selected java version
34+
35+
`sudo update-alternatives --config java`
36+
37+
1. Execute the jar file
38+
39+
`java -jar target/compute-signed-metadata-1.0-SNAPSHOT-jar-with-dependencies.jar`

compute/signed-metadata/pom.xml

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<!--
2+
Copyright 2016 Google Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
17+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
18+
<modelVersion>4.0.0</modelVersion>
19+
<groupId>com.google.cloud.example.jwt</groupId>
20+
<artifactId>compute-signed-metadata</artifactId>
21+
<version>1.0-SNAPSHOT</version>
22+
<name>compute-signed-metadata</name>
23+
<parent>
24+
<groupId>com.google.cloud</groupId>
25+
<artifactId>compute</artifactId>
26+
<version>1.0.0</version>
27+
<relativePath>..</relativePath>
28+
</parent>
29+
<dependencies>
30+
<dependency>
31+
<groupId>com.auth0</groupId>
32+
<artifactId>java-jwt</artifactId>
33+
<version>3.2.0</version>
34+
</dependency>
35+
<dependency>
36+
<groupId>com.google.code.gson</groupId>
37+
<artifactId>gson</artifactId>
38+
<version>2.8.1</version>
39+
</dependency>
40+
<dependency>
41+
<groupId>com.google.guava</groupId>
42+
<artifactId>guava</artifactId>
43+
<version>23.0</version>
44+
</dependency>
45+
</dependencies>
46+
<build>
47+
<plugins>
48+
<plugin>
49+
<groupId>org.apache.maven.plugins</groupId>
50+
<artifactId>maven-jar-plugin</artifactId>
51+
<version>3.0.2</version>
52+
</plugin>
53+
<plugin>
54+
<groupId>org.apache.maven.plugins</groupId>
55+
<artifactId>maven-assembly-plugin</artifactId>
56+
<version>3.0.0</version>
57+
<executions>
58+
<execution>
59+
<phase>package</phase>
60+
<goals>
61+
<goal>single</goal>
62+
</goals>
63+
</execution>
64+
</executions>
65+
<configuration>
66+
<archive>
67+
<manifest>
68+
<mainClass>com.example.compute.signedmetadata.App</mainClass>
69+
</manifest>
70+
</archive>
71+
<descriptorRefs>
72+
<descriptorRef>jar-with-dependencies</descriptorRef>
73+
</descriptorRefs>
74+
</configuration>
75+
</plugin>
76+
<plugin>
77+
<groupId>org.apache.maven.plugins</groupId>
78+
<artifactId>maven-compiler-plugin</artifactId>
79+
<version>3.6.2</version>
80+
<configuration>
81+
<source>1.8</source>
82+
<target>1.8</target>
83+
</configuration>
84+
</plugin>
85+
</plugins>
86+
</build>
87+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2017 Google Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package com.example.compute.signedmetadata;
15+
16+
public final class App {
17+
18+
// AUDIENCE should be set according to your domain and needs.
19+
// Please check https://cloud.google.com/compute/docs/instances/verifying-instance-identity
20+
// for details.
21+
private static final String AUDIENCE = "http://example.com";
22+
23+
public static void main(String[] args) {
24+
// We get token from one instance and verify it trusted machine.
25+
String token = new GCPInstance(AUDIENCE).provideToken();
26+
new VerifyingInstance(AUDIENCE).verifyToken(token);
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2017 Google Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package com.example.compute.signedmetadata;
15+
16+
import com.example.compute.signedmetadata.token.TokenDownloader;
17+
18+
import java.io.IOException;
19+
import java.net.URISyntaxException;
20+
21+
class GCPInstance {
22+
23+
private String audience;
24+
25+
GCPInstance(String audience) {
26+
this.audience = audience;
27+
}
28+
29+
String provideToken() {
30+
try {
31+
return new TokenDownloader().getTokenWithAudience(audience);
32+
} catch (URISyntaxException | IOException e) {
33+
throw new RuntimeException(e);
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2017 Google Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package com.example.compute.signedmetadata;
15+
16+
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
17+
import com.auth0.jwt.exceptions.InvalidClaimException;
18+
import com.auth0.jwt.exceptions.JWTVerificationException;
19+
import com.auth0.jwt.exceptions.SignatureVerificationException;
20+
import com.auth0.jwt.exceptions.TokenExpiredException;
21+
import com.example.compute.signedmetadata.token.DecodedGoogleJWTWrapper;
22+
import com.example.compute.signedmetadata.token.TokenVerifier;
23+
24+
class VerifyingInstance {
25+
26+
private String audience;
27+
28+
VerifyingInstance(String audience) {
29+
this.audience = audience;
30+
}
31+
32+
void verifyToken(String token) {
33+
TokenVerifier gtv = new TokenVerifier();
34+
// JWTVerificationException is runtime exception, we don't need to catch it if we want to exit
35+
// process in case of verification problem. However, to handle verification problems
36+
// programmatically we can can JWTVerificationException or specific subclass.
37+
// Following are examples how to handle verification failure.
38+
try {
39+
DecodedGoogleJWTWrapper decodedJWT = gtv.verifyWithAudience(audience, token);
40+
System.out.println("Project id : " + decodedJWT.getProjectId());
41+
System.out.println("Project number : " + decodedJWT.getProjectNumber());
42+
// This are examples how to handle exceptions, which indicate verification failure.
43+
} catch (AlgorithmMismatchException e) {
44+
// We assume that downloaded certs are RSA256, this exception will happen if this changes.
45+
throw e;
46+
} catch (SignatureVerificationException e) {
47+
// Could not verify signature of a token, possibly someone provided forged token.
48+
throw e;
49+
} catch (TokenExpiredException e) {
50+
// We encountered old token, possibly replay attack.
51+
throw e;
52+
} catch (InvalidClaimException e) {
53+
// Different Audience for token and for verification, possibly token for other verifier.
54+
throw e;
55+
} catch (JWTVerificationException e) {
56+
// Some other problem during verification
57+
// JWTVerificationException is super-class to:
58+
// - SignatureVerificationException
59+
// - TokenExpiredException
60+
// - InvalidClaimException
61+
throw e;
62+
}
63+
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2017 Google Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package com.example.compute.signedmetadata.token;
15+
16+
import com.auth0.jwt.interfaces.DecodedJWT;
17+
import com.google.common.base.Suppliers;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import java.util.Set;
22+
import java.util.function.Supplier;
23+
24+
public class DecodedGoogleJWTWrapper {
25+
26+
private static final String KEY_PROJECT_ID = "project_id";
27+
private static final String KEY_PROJECT_NUMBER = "project_number";
28+
private static final String GOOGLE_METADATA_SPACE = "google";
29+
private static final String COMPUTE_ENGINE_METADATA_SUBSPACE = "compute_engine";
30+
31+
private DecodedJWT jwt;
32+
private Supplier<Map<String, Object>> computeEngineMetadata = Suppliers.memoize(
33+
() -> {
34+
Map<String, Object> googleMetadata = jwt.getClaims().get(GOOGLE_METADATA_SPACE).asMap();
35+
Object computeEngineObject = googleMetadata.get(COMPUTE_ENGINE_METADATA_SUBSPACE);
36+
return castToMetadataMap(computeEngineObject);
37+
});
38+
39+
DecodedGoogleJWTWrapper(DecodedJWT jwt) {
40+
this.jwt = jwt;
41+
}
42+
43+
public String getProjectId() {
44+
return getComputeEngineMetadata(KEY_PROJECT_ID);
45+
}
46+
47+
public String getProjectNumber() {
48+
return getComputeEngineMetadata(KEY_PROJECT_NUMBER);
49+
}
50+
51+
private String getComputeEngineMetadata(String key) {
52+
return computeEngineMetadata.get().get(key).toString();
53+
}
54+
55+
// In Java we can only assure that an object is of class Map, we can check for key and value
56+
// types of an object added to Map, but only if Map is not empty.
57+
@SuppressWarnings({"rawtypes","unchecked"})
58+
private Map<String, Object> castToMetadataMap(Object object) {
59+
if (object instanceof Map) {
60+
Map map = (Map) object;
61+
if (map.isEmpty()) {
62+
// Map is empty, so we will create new map with desired types
63+
return new HashMap<>();
64+
}
65+
Set<Map.Entry> set = map.entrySet();
66+
Map.Entry someEntry = set.iterator().next();
67+
if (someEntry.getKey() instanceof String) {
68+
return (Map<String, Object>) object;
69+
}
70+
}
71+
throw new RuntimeException("We have not received a map of metadata");
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2017 Google Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package com.example.compute.signedmetadata.token;
15+
16+
import java.io.BufferedReader;
17+
import java.io.InputStreamReader;
18+
import java.io.IOException;
19+
import java.net.URL;
20+
import java.net.URLConnection;
21+
import java.nio.charset.StandardCharsets;
22+
23+
class Downloader {
24+
25+
String download(String urlString) throws IOException {
26+
URL url = new URL(urlString);
27+
return download(url.openConnection());
28+
}
29+
30+
String download(URLConnection connection) throws IOException {
31+
try(BufferedReader in = new BufferedReader(
32+
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
33+
StringBuilder sb = new StringBuilder();
34+
in.lines().forEachOrdered(sb::append);
35+
return sb.toString();
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)