diff --git a/security/tools/jwt/samples/README.md b/security/tools/jwt/samples/README.md new file mode 100644 index 000000000000..68dc4a5308dd --- /dev/null +++ b/security/tools/jwt/samples/README.md @@ -0,0 +1,65 @@ +# Sample JWT and JWKS data for demo + +This folder contains sample data to setup end-user authentication with Istio authentication policy, together with the script to (re)generate them. + +## Example end-user authentication policy using the mock jwks.json data + +```yaml +apiVersion: "authentication.istio.io/v1alpha1" +kind: "Policy" +metadata: + name: "jwt-example" +spec: + targets: + - name: httpbin + origins: + - jwt: + issuer: "testing@secure.istio.io" + jwksUri: "https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/jwks.json" + principalBinding: USE_ORIGIN +``` + +The `demo.jwt` contains a signed-JWT token with following payload: + +```json +{ + "exp": 4685989700, + "foo": "bar", + "iat": 1532389700, + "iss": "testing@secure.istio.io", + "sub": "testing@secure.istio.io" +} +``` + +Note the expiration date (`exp`) is very long in the future, so it can be tested as is without anty modification. For example: + +```bash +TOKEN=$(curl https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/demo.jwt -s) +curl --header "Authorization: Bearer $TOKEN" $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n" +``` + +Alternatively, you can use the `gen-jwt.py` script to create new test token: + +``` +TOKEN=$(./gen-jwt.py key.pem --expire=300 --iss "new-issuer@secure.istio.io") +``` + +> Before you start, run the following command to install python dependences. + + ``` + pip install jwcrypto + ``` + +## Regenerate private key and JWKS (for developer use only) + +1. Regenerate private key using `openssl` + +``` +openssl genrsa -out key.pem 2048 +``` + +2. Run gen-jwt.py with `--jkws` to create new public key set and demo JWT + +``` +gen-jwt.py key.pem -jwks=./jwks.json --expire=3153600000 --claims=foo:bar > demo.jwt +``` \ No newline at end of file diff --git a/security/tools/jwt/samples/demo.jwt b/security/tools/jwt/samples/demo.jwt new file mode 100644 index 000000000000..f3e8d3648b0f --- /dev/null +++ b/security/tools/jwt/samples/demo.jwt @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg diff --git a/security/tools/jwt/samples/gen-jwt.py b/security/tools/jwt/samples/gen-jwt.py new file mode 100755 index 000000000000..66b84bed01f5 --- /dev/null +++ b/security/tools/jwt/samples/gen-jwt.py @@ -0,0 +1,96 @@ +#!/usr/bin/python + +# Copyright 2018 Istio Authors +# +# 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. + +"""Python script generates a JWT signed with custom private key. + +Example: +./gen-jwt.py --iss example-issuer --aud foo,bar --claims=email:foo@google.com,dead:beef key.pem +""" +import argparse +import time + +from jwcrypto import jwt, jwk + +def main(args): + """Generates a signed JSON Web Token from local private key.""" + with open(args.key) as f: + pem_data = f.read() + f.closed + + key = jwk.JWK.from_pem(pem_data) + + if args.jwks: + with open(args.jwks, "w+") as fout: + fout.write("{ \"keys\":[ ") + fout.write(key.export(private_key=False)) + fout.write("]}") + fout.close + + now = int(time.time()) + payload = { + # expire in one hour. + "exp": now + args.expire, + "iat": now, + } + if args.iss: + payload["iss"] = args.iss + if args.sub: + payload["sub"] = args.sub + else: + payload["sub"] = args.iss + + if args.aud: + if "," in args.aud: + payload["aud"] = args.aud.split(",") + else: + payload["aud"] = args.aud + + if args.claims: + for item in args.claims.split(","): + k, v = item.split(':') + payload[k] = v + + token = jwt.JWT(header={"alg": "RS256", "typ": "JWT", "kid": key.key_id}, + claims=payload) + + token.make_signed_token(key) + + return token.serialize() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + # positional arguments + parser.add_argument( + 'key', + help='The path to the key pem file. The key can be generated with openssl command: `openssl genrsa -out key.pem 2048`') + # optional arguments + parser.add_argument("-iss", "--iss", + default="testing@secure.istio.io", + help="iss claim. Default is `testing@secure.istio.io`") + parser.add_argument("-aud", "--aud", + help="aud claim. This is comma-separated-list of audiences") + parser.add_argument("-sub", "--sub", + help="sub claim. If not provided, it is set to the same as iss claim.") + parser.add_argument("-claims", "--claims", + help="Other claims in format name1:value1,name2:value2 etc. Only string values are supported.") + parser.add_argument("-jwks", "--jwks", + help="Path to the output file for JWKS.") + parser.add_argument("-expire", "--expire", type=int, default=3600, + help="JWT expiration time in second. Default is 1 hour.") + print main(parser.parse_args()) diff --git a/security/tools/jwt/samples/jwks.json b/security/tools/jwt/samples/jwks.json new file mode 100644 index 000000000000..b2c93b48a1d6 --- /dev/null +++ b/security/tools/jwt/samples/jwks.json @@ -0,0 +1 @@ +{ "keys":[ {"e":"AQAB","kid":"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ","kty":"RSA","n":"xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ"}]} \ No newline at end of file diff --git a/security/tools/jwt/samples/key.pem b/security/tools/jwt/samples/key.pem new file mode 100644 index 000000000000..68fa79265a83 --- /dev/null +++ b/security/tools/jwt/samples/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxAE7eB6qugXyCAG3yhh7pkDkT65pHymX+P7KfIupjf59vsdo +91bSP9C8H07pSAGQO1MV/xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg/pbh +LdKXbi66GlVeK6ABZOUW3WYtnNHD+91gVuoeJT/DwtGGcp4ignkgXfkiEm4sw+4s +fb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo/+4WTiULmmHSGZHOjzwa8Wtr +tOQGsAFjIbno85jp6MnGGGZPYZbDAa/b3y5u+YpW7ypZrvD8BgtKVjgtQgZhLAGe +zMt0ua3DRrWnKqTZ0BJ/EyxOGuHJrLsn00fnMQIDAQABAoIBAQCfv3ler2/qaYoX +6H6I4md02xK5tqbK1TWdpNwXoiSxLCgEY7YzULnPdSq+Qax0GYIaN9+Hof7cLFRf +XOxCTqCm+k8cqqwP0vTFhdFY8ltLkCdAGGyy0h7FmKPpboZv+9rnBqgaDntCgty+ +3HD2pZ2oMk407Fwt8qChwmMU9EZGycIRxwTFB8xGpss8gFc+fTAmvGFI/s8aclu+ ++XmvPkw0lz+dLaNsne0yBHVMPFtncqhbsFbnbB8OAMtWsVbTwU60OVeZ/oOYJVY9 +vtuMQ4IAi5kMx8AWQ1UDvW2ZnuGpQkr6dZ2YP/IMOXGaywbgcHPUitrW3iIYYW+U +NIVWXkA5AoGBAPd9MesQduCsngWNvtIjrNUEakbXkg0vbw+dRIbIqy2Bx/tyWhim +qFNYcNjZELqp5C3f3hlO/k9K8xxJLIYloYg1948DbpOhVk8SdFGgpCYC4q/fBPw+ +kAFQEKhZelwKj2Srf58BCrhIe1KPHbQfTF/JZ7JBndIw79/1p9rU6wWLAoGBAMq+ +yd+7L8wpCZNtmjDUTWmGq3LmFLBzBMhwmV2NOKbTFakMVyEtSORQFOJbxFwuWx5z +j3nLq9ivQgNKLPLSTF/Dtej2lzgNd9YG/9XW+6HeZ841XNUHE0XsU6Mm1EAyadfu +AAnoxTh/NqYvF81qCM9tl9Bsjx8klOyXrrGKj/WzAoGAKJC2u+bI9W6VwCdJnbwH +OistGEuBPvQFajPG5ajClgTtuIM3zU6TzIV0ibaajV4HbpWBG/jcqjaIvpwn1h0Y +6vCdkS1o1H3fXbqSokaIYUqbyWPut0Gx7OUotc9kxO1eL4wEsRVEoowO2qtmnP18 +UT775jXnHmqzBqyHRNEdbJ0CgYAdgPAdp77H5fznwF5c1rhBMADJIqRGHSbICGK5 +E3D4DeWsCQiw4kcmOmUfn50OkQxffQ+W+MWULcTcd7Hc0C+fC/rv4NqWpJcYxUH7 +m2JY5uWSQ3+z3Gi4lzCAoIjooq12Z8MHriDtHM4WFupO0SxhCyC5iuK09HzbhSM9 +4N0cMwKBgGfYJRmX5pW7rKo/O1Zl6oG6VBA6fYXIe28X9sZ0YCbVIuX8MgRk2Vpm +y47qmzEQ/1kwWtQzZOcePM8rd8qTTFPCkM45S1ieh7qjNSMVIbl8I635UqCCoetW +VDwvUhVLO4mBT0p2F8g6Wngmpy9ZBh2B52w3Rz9UxHffwdC7hhQD +-----END RSA PRIVATE KEY----- diff --git a/security/tools/jwt/samples/requirements.txt b/security/tools/jwt/samples/requirements.txt new file mode 100644 index 000000000000..8c5d261c5b15 --- /dev/null +++ b/security/tools/jwt/samples/requirements.txt @@ -0,0 +1 @@ +jwcrypto \ No newline at end of file