Compare commits

...

255 Commits
v0.6.2 ... main

Author SHA1 Message Date
dependabot[bot] 3fbff4c6ca
Bump jupiterVersion from 5.13.1 to 5.13.2 (#334)
Bumps `jupiterVersion` from 5.13.1 to 5.13.2.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.13.1 to 5.13.2
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.1...r5.13.2)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.13.1 to 5.13.2
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.1...r5.13.2)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.13.1 to 5.13.2
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.1...r5.13.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-version: 5.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-version: 5.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-version: 5.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2025-07-02 12:34:15 -05:00
dependabot[bot] 25e05a99a7
Bump com.nimbusds:nimbus-jose-jwt from 10.3 to 10.3.1 (#335)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 10.3 to 10.3.1.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/10.3.1..10.3)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-version: 10.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 16:23:51 -05:00
dependabot[bot] a6059bdaeb
Bump jupiterVersion from 5.13.0 to 5.13.1 (#333)
Bumps `jupiterVersion` from 5.13.0 to 5.13.1.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.13.0 to 5.13.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.13.0...r5.13.1)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.13.0 to 5.13.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.13.0...r5.13.1)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.13.0 to 5.13.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.13.0...r5.13.1)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-version: 5.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-version: 5.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-version: 5.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-10 11:48:30 -05:00
dependabot[bot] c00a4d09b6
Bump io.netty:netty-transport-native-kqueue (#332)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.2.1.Final to 4.2.2.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.2.1.Final...netty-4.2.2.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-version: 4.2.2.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-06 09:16:14 -05:00
Max Lambrecht 6d428b4494
Prepare release v0.8.12 (#331)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2025-06-05 12:59:30 -05:00
dependabot[bot] 30f5bcf688
Bump jupiterVersion from 5.12.2 to 5.13.0 (#330)
Bumps `jupiterVersion` from 5.12.2 to 5.13.0.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.12.2 to 5.13.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.2...r5.13.0)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.12.2 to 5.13.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.2...r5.13.0)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.12.2 to 5.13.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.2...r5.13.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-version: 5.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-version: 5.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-version: 5.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-04 15:18:49 -05:00
dependabot[bot] 06a1f4f489
Bump grpcVersion from 1.72.0 to 1.73.0 (#327)
Bumps `grpcVersion` from 1.72.0 to 1.73.0.

Updates `io.grpc:protoc-gen-grpc-java` from 1.72.0 to 1.73.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.72.0...v1.73.0)

Updates `io.grpc:grpc-protobuf` from 1.72.0 to 1.73.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.72.0...v1.73.0)

Updates `io.grpc:grpc-stub` from 1.72.0 to 1.73.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.72.0...v1.73.0)

Updates `io.grpc:grpc-inprocess` from 1.72.0 to 1.73.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.72.0...v1.73.0)

Updates `io.grpc:grpc-testing` from 1.72.0 to 1.73.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.72.0...v1.73.0)

Updates `io.grpc:grpc-netty-shaded` from 1.72.0 to 1.73.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.72.0...v1.73.0)

Updates `io.grpc:grpc-netty` from 1.72.0 to 1.73.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.72.0...v1.73.0)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-version: 1.73.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-version: 1.73.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-version: 1.73.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-inprocess
  dependency-version: 1.73.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-version: 1.73.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-version: 1.73.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-version: 1.73.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-04 15:10:52 -05:00
Peter 0d5eeccfe4
sslNegotiation is needed to pickup SpiffeSslSocketFactory (#325)
* sslNegotiation is needed to pickup SpiffeSslSocketFactory

Signed-off-by: Peter <peter.gassner@outlook.com>
2025-05-21 08:10:31 -05:00
dependabot[bot] db84782365
Bump io.netty:netty-transport-native-kqueue (#321)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.2.0.Final to 4.2.1.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.2.0.Final...netty-4.2.1.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-version: 4.2.1.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2025-05-20 15:00:02 -05:00
dependabot[bot] eb6731f712
Bump jupiterVersion from 5.12.1 to 5.12.2 (#319)
Bumps `jupiterVersion` from 5.12.1 to 5.12.2.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.12.1 to 5.12.2
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.1...r5.12.2)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.12.1 to 5.12.2
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.1...r5.12.2)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.12.1 to 5.12.2
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.1...r5.12.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-version: 5.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-version: 5.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-version: 5.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2025-05-20 14:49:43 -05:00
dependabot[bot] 0e00b0141e
Bump com.nimbusds:nimbus-jose-jwt from 10.2 to 10.3 (#323)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 10.2 to 10.3.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/10.3..10.2)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-version: '10.3'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-20 12:04:00 -05:00
Max Lambrecht 58171162d1
Revert 'system-stubs-core' update (#322)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2025-05-19 14:16:14 -05:00
dependabot[bot] 51779e5deb
Bump uk.org.webcompere:system-stubs-core from 2.0.3 to 2.1.8 (#320)
Bumps [uk.org.webcompere:system-stubs-core](https://github.com/webcompere/system-stubs) from 2.0.3 to 2.1.8.
- [Release notes](https://github.com/webcompere/system-stubs/releases)
- [Changelog](https://github.com/webcompere/system-stubs/blob/main/History.md)
- [Commits](https://github.com/webcompere/system-stubs/compare/system-stubs-parent-2.0.3...system-stubs-parent-2.1.8)

---
updated-dependencies:
- dependency-name: uk.org.webcompere:system-stubs-core
  dependency-version: 2.1.8
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-08 15:36:44 -05:00
dependabot[bot] 7560dd6359
Bump grpcVersion from 1.71.0 to 1.72.0 (#318)
Bumps `grpcVersion` from 1.71.0 to 1.72.0.

Updates `io.grpc:protoc-gen-grpc-java` from 1.71.0 to 1.72.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.71.0...v1.72.0)

Updates `io.grpc:grpc-protobuf` from 1.71.0 to 1.72.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.71.0...v1.72.0)

Updates `io.grpc:grpc-stub` from 1.71.0 to 1.72.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.71.0...v1.72.0)

Updates `io.grpc:grpc-inprocess` from 1.71.0 to 1.72.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.71.0...v1.72.0)

Updates `io.grpc:grpc-testing` from 1.71.0 to 1.72.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.71.0...v1.72.0)

Updates `io.grpc:grpc-netty-shaded` from 1.71.0 to 1.72.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.71.0...v1.72.0)

Updates `io.grpc:grpc-netty` from 1.71.0 to 1.72.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.71.0...v1.72.0)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-version: 1.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-version: 1.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-version: 1.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-inprocess
  dependency-version: 1.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-version: 1.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-version: 1.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-version: 1.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-06 19:25:41 -05:00
dependabot[bot] d39df4c7ae
Bump com.google.protobuf:protobuf-gradle-plugin from 0.9.4 to 0.9.5 (#314)
Bumps [com.google.protobuf:protobuf-gradle-plugin](https://github.com/google/protobuf-gradle-plugin) from 0.9.4 to 0.9.5.
- [Release notes](https://github.com/google/protobuf-gradle-plugin/releases)
- [Commits](https://github.com/google/protobuf-gradle-plugin/compare/v0.9.4...v0.9.5)

---
updated-dependencies:
- dependency-name: com.google.protobuf:protobuf-gradle-plugin
  dependency-version: 0.9.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2025-04-18 11:20:18 -05:00
dependabot[bot] 0442dc5134
Bump io.netty:netty-transport-native-kqueue (#315)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.119.Final to 4.2.0.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.119.Final...netty-4.2.0.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-version: 4.2.0.Final
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2025-04-18 11:16:33 -05:00
dependabot[bot] ad6e33f5b5
Bump com.nimbusds:nimbus-jose-jwt from 10.0.2 to 10.2 (#317)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 10.0.2 to 10.2.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/10.2..10.0.2)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-version: '10.2'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-18 11:12:54 -05:00
dependabot[bot] 673f439f5e
Bump org.projectlombok:lombok from 1.18.36 to 1.18.38 (#313)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.36 to 1.18.38.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.36...v1.18.38)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-version: 1.18.38
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-18 11:08:25 -05:00
dependabot[bot] e0ac998038
Bump jupiterVersion from 5.12.0 to 5.12.1 (#310)
Bumps `jupiterVersion` from 5.12.0 to 5.12.1.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.12.0 to 5.12.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.0...r5.12.1)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.12.0 to 5.12.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.0...r5.12.1)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.12.0 to 5.12.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.0...r5.12.1)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-15 09:12:39 -05:00
dependabot[bot] f8b4576f6d
Bump grpcVersion from 1.70.0 to 1.71.0 (#308)
Bumps `grpcVersion` from 1.70.0 to 1.71.0.

Updates `io.grpc:protoc-gen-grpc-java` from 1.70.0 to 1.71.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.70.0...v1.71.0)

Updates `io.grpc:grpc-protobuf` from 1.70.0 to 1.71.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.70.0...v1.71.0)

Updates `io.grpc:grpc-stub` from 1.70.0 to 1.71.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.70.0...v1.71.0)

Updates `io.grpc:grpc-inprocess` from 1.70.0 to 1.71.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.70.0...v1.71.0)

Updates `io.grpc:grpc-testing` from 1.70.0 to 1.71.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.70.0...v1.71.0)

Updates `io.grpc:grpc-netty-shaded` from 1.70.0 to 1.71.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.70.0...v1.71.0)

Updates `io.grpc:grpc-netty` from 1.70.0 to 1.71.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.70.0...v1.71.0)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-inprocess
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-05 15:19:44 -06:00
dependabot[bot] 1d6d4b7134
Bump io.netty:netty-transport-native-kqueue (#307)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.118.Final to 4.1.119.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.118.Final...netty-4.1.119.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2025-02-27 16:31:50 -06:00
dependabot[bot] 9e3f67f5c7
Bump com.nimbusds:nimbus-jose-jwt from 10.0.1 to 10.0.2 (#306)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 10.0.1 to 10.0.2.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/10.0.2..10.0.1)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2025-02-27 14:19:23 -06:00
dependabot[bot] fc6283ea32
Bump jupiterVersion from 5.11.4 to 5.12.0 (#305)
Bumps `jupiterVersion` from 5.11.4 to 5.12.0.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.11.4 to 5.12.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.4...r5.12.0)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.11.4 to 5.12.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.4...r5.12.0)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.11.4 to 5.12.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.4...r5.12.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-27 14:14:36 -06:00
dependabot[bot] a7d47d43db
Bump io.netty:netty-transport-native-kqueue (#303)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.117.Final to 4.1.118.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.117.Final...netty-4.1.118.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-14 08:20:56 -06:00
dependabot[bot] 2de2d3f044
Bump grpcVersion from 1.69.0 to 1.70.0 (#302)
Bumps `grpcVersion` from 1.69.0 to 1.70.0.

Updates `io.grpc:protoc-gen-grpc-java` from 1.69.0 to 1.70.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.69.0...v1.70.0)

Updates `io.grpc:grpc-protobuf` from 1.69.0 to 1.70.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.69.0...v1.70.0)

Updates `io.grpc:grpc-stub` from 1.69.0 to 1.70.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.69.0...v1.70.0)

Updates `io.grpc:grpc-inprocess` from 1.69.0 to 1.70.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.69.0...v1.70.0)

Updates `io.grpc:grpc-testing` from 1.69.0 to 1.70.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.69.0...v1.70.0)

Updates `io.grpc:grpc-netty-shaded` from 1.69.0 to 1.70.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.69.0...v1.70.0)

Updates `io.grpc:grpc-netty` from 1.69.0 to 1.70.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.69.0...v1.70.0)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-inprocess
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2025-01-23 14:14:18 -06:00
dependabot[bot] b169de3dce
Bump io.netty:netty-transport-native-kqueue (#300)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.116.Final to 4.1.117.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.116.Final...netty-4.1.117.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-23 14:09:51 -06:00
dependabot[bot] 0b5e60c486
Bump com.nimbusds:nimbus-jose-jwt from 9.47 to 10.0.1 (#297)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.47 to 10.0.1.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/10.0.1..9.47)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2025-01-07 07:42:37 -06:00
dependabot[bot] d9bad06282
Bump io.netty:netty-transport-native-kqueue (#294)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.115.Final to 4.1.116.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.115.Final...netty-4.1.116.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-07 07:42:04 -06:00
dependabot[bot] e5beed3362
Bump jupiterVersion from 5.11.3 to 5.11.4 (#293)
Bumps `jupiterVersion` from 5.11.3 to 5.11.4.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.11.3 to 5.11.4
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.3...r5.11.4)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.11.3 to 5.11.4
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.3...r5.11.4)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.11.3 to 5.11.4
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.3...r5.11.4)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-07 07:32:23 -06:00
dependabot[bot] d629678fe5
Bump grpcVersion from 1.68.1 to 1.69.0 (#292)
Bumps `grpcVersion` from 1.68.1 to 1.69.0.

Updates `io.grpc:protoc-gen-grpc-java` from 1.68.1 to 1.69.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.1...v1.69.0)

Updates `io.grpc:grpc-protobuf` from 1.68.1 to 1.69.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.1...v1.69.0)

Updates `io.grpc:grpc-stub` from 1.68.1 to 1.69.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.1...v1.69.0)

Updates `io.grpc:grpc-inprocess` from 1.68.1 to 1.69.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.1...v1.69.0)

Updates `io.grpc:grpc-testing` from 1.68.1 to 1.69.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.1...v1.69.0)

Updates `io.grpc:grpc-netty-shaded` from 1.68.1 to 1.69.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.1...v1.69.0)

Updates `io.grpc:grpc-netty` from 1.68.1 to 1.69.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.1...v1.69.0)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-inprocess
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-10 16:03:45 -06:00
Max Lambrecht 4e3d0cf703
Prepare release v0.8.11 (#288)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-11-18 16:08:12 -06:00
Max Lambrecht 4f86219c18
Bump spire version in integration test (#287)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-11-18 16:07:49 -06:00
dependabot[bot] 12b1b438a2
Bump org.projectlombok:lombok from 1.18.34 to 1.18.36 (#286)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.34 to 1.18.36.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.34...v1.18.36)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 08:54:39 -06:00
dependabot[bot] 1894482bc0
Bump com.nimbusds:nimbus-jose-jwt from 9.45 to 9.47 (#285)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.45 to 9.47.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/9.47..9.45)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-11-18 08:49:57 -06:00
dependabot[bot] 83cd18af0c
Bump io.netty:netty-transport-native-kqueue (#281)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.114.Final to 4.1.115.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.114.Final...netty-4.1.115.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-11-18 08:49:26 -06:00
Alexander Dümont 0526ec5d3a
Fix CRLF newline removal in DER format logic (#284)
Signed-off-by: Alexander Dümont <alexander_duemont@web.de>
2024-11-18 08:48:49 -06:00
Max Lambrecht de8b12c70a
Prepare release v0.8.10 (#279)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-11-06 15:05:28 -06:00
dependabot[bot] dcd1f8e020
Bump com.nimbusds:nimbus-jose-jwt from 9.42 to 9.45 (#278)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.42 to 9.45.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/9.45..9.42)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-11-06 09:26:19 -06:00
dependabot[bot] aa654e43ba
Bump grpcVersion from 1.68.0 to 1.68.1 (#276)
Bumps `grpcVersion` from 1.68.0 to 1.68.1.

Updates `io.grpc:protoc-gen-grpc-java` from 1.68.0 to 1.68.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.0...v1.68.1)

Updates `io.grpc:grpc-protobuf` from 1.68.0 to 1.68.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.0...v1.68.1)

Updates `io.grpc:grpc-stub` from 1.68.0 to 1.68.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.0...v1.68.1)

Updates `io.grpc:grpc-inprocess` from 1.68.0 to 1.68.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.0...v1.68.1)

Updates `io.grpc:grpc-testing` from 1.68.0 to 1.68.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.0...v1.68.1)

Updates `io.grpc:grpc-netty-shaded` from 1.68.0 to 1.68.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.0...v1.68.1)

Updates `io.grpc:grpc-netty` from 1.68.0 to 1.68.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.68.0...v1.68.1)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-inprocess
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 09:22:40 -06:00
dependabot[bot] cf7697b2aa
Bump com.nimbusds:nimbus-jose-jwt from 9.41.2 to 9.42 (#275)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.41.2 to 9.42.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/commits/tag/9.42)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 09:52:14 -05:00
dependabot[bot] adf1207b2f
Bump jupiterVersion from 5.11.2 to 5.11.3 (#273)
Bumps `jupiterVersion` from 5.11.2 to 5.11.3.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.11.2 to 5.11.3
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.2...r5.11.3)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.11.2 to 5.11.3
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.2...r5.11.3)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.11.2 to 5.11.3
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.2...r5.11.3)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 10:55:50 -05:00
Max Lambrecht 0023bec0c6
Prepare Release v0.8.9 (#272)
* Prepare release v0.8.9

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>

* Minor

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>

---------

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-10-09 11:34:37 -05:00
Max Lambrecht 7f53871ce0
Bump com.google.protobuf:protoc to 3.25.5 (#271)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-10-08 12:58:04 -05:00
Max Lambrecht fcee57f94c
Prepare release v0.8.8 (#270)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-10-08 12:01:43 -05:00
Max Lambrecht 026f3c51e7
Add testParseJWKSWithEmptyKeysArray (#233)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-10-08 09:58:20 -05:00
Max Lambrecht 5ec96673c9
Update gradle version to 8.10.2 (#269)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-10-07 16:16:23 -07:00
dependabot[bot] 45c7785c1c
Bump jupiterVersion from 5.11.1 to 5.11.2 (#268)
Bumps `jupiterVersion` from 5.11.1 to 5.11.2.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.11.1 to 5.11.2
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.1...r5.11.2)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.11.1 to 5.11.2
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.1...r5.11.2)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.11.1 to 5.11.2
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.1...r5.11.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 11:27:22 -05:00
dependabot[bot] a4eaade918
Bump uk.org.webcompere:system-stubs-core from 2.0.3 to 2.1.7 (#264)
* Bump uk.org.webcompere:system-stubs-core from 2.0.3 to 2.1.7

Bumps [uk.org.webcompere:system-stubs-core](https://github.com/webcompere/system-stubs) from 2.0.3 to 2.1.7.
- [Release notes](https://github.com/webcompere/system-stubs/releases)
- [Changelog](https://github.com/webcompere/system-stubs/blob/main/History.md)
- [Commits](https://github.com/webcompere/system-stubs/compare/system-stubs-parent-2.0.3...system-stubs-parent-2.1.7)

---
updated-dependencies:
- dependency-name: uk.org.webcompere:system-stubs-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Amend version update for test dependency

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-10-01 18:18:14 -05:00
dependabot[bot] d946a25230
Bump com.nimbusds:nimbus-jose-jwt from 9.41.1 to 9.41.2 (#266)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.41.1 to 9.41.2.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/commits)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-10-01 18:14:39 -05:00
dependabot[bot] 36e114bbb6
Bump io.netty:netty-transport-native-kqueue (#265)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.113.Final to 4.1.114.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.113.Final...netty-4.1.114.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-10-01 18:09:20 -05:00
dependabot[bot] 9e1b2f5cf1
Bump jupiterVersion from 5.11.0 to 5.11.1 (#263)
Bumps `jupiterVersion` from 5.11.0 to 5.11.1.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.11.0 to 5.11.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.0...r5.11.1)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.11.0 to 5.11.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.0...r5.11.1)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.11.0 to 5.11.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.0...r5.11.1)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 18:04:45 -05:00
dependabot[bot] 18947c887e
Bump grpcVersion from 1.66.0 to 1.68.0 (#262)
Bumps `grpcVersion` from 1.66.0 to 1.68.0.

Updates `io.grpc:protoc-gen-grpc-java` from 1.66.0 to 1.68.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/commits)

Updates `io.grpc:grpc-protobuf` from 1.66.0 to 1.68.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/commits)

Updates `io.grpc:grpc-stub` from 1.66.0 to 1.68.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/commits)

Updates `io.grpc:grpc-inprocess` from 1.66.0 to 1.68.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/commits)

Updates `io.grpc:grpc-testing` from 1.66.0 to 1.68.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/commits)

Updates `io.grpc:grpc-netty-shaded` from 1.66.0 to 1.68.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/commits)

Updates `io.grpc:grpc-netty` from 1.66.0 to 1.68.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/commits)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-inprocess
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 11:41:39 -05:00
Max Lambrecht 03118e3f12
Prepare release v0.8.7 (#261)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-09-20 12:42:25 -07:00
dependabot[bot] 86e6e32add
Bump commons-cli:commons-cli from 1.8.0 to 1.9.0 (#258)
Bumps commons-cli:commons-cli from 1.8.0 to 1.9.0.

---
updated-dependencies:
- dependency-name: commons-cli:commons-cli
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-09-20 14:14:27 -05:00
dependabot[bot] 126a4d90ac
Bump com.nimbusds:nimbus-jose-jwt from 9.40 to 9.41.1 (#259)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.40 to 9.41.1.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/9.41.1..9.40)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-09-20 14:14:12 -05:00
dependabot[bot] 44683c69c8
Bump io.netty:netty-transport-native-kqueue (#260)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.112.Final to 4.1.113.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.112.Final...netty-4.1.113.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-20 14:13:52 -05:00
dependabot[bot] f0a16ee86c
Bump jupiterVersion from 5.10.2 to 5.11.0 (#254)
Bumps `jupiterVersion` from 5.10.2 to 5.11.0.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.10.2 to 5.11.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.11.0)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.10.2 to 5.11.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.11.0)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.10.2 to 5.11.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.11.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 23:49:13 -07:00
dependabot[bot] f097e3b7af
Bump commons-validator:commons-validator from 1.8.0 to 1.9.0 (#251)
Bumps commons-validator:commons-validator from 1.8.0 to 1.9.0.

---
updated-dependencies:
- dependency-name: commons-validator:commons-validator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 23:40:20 -07:00
dependabot[bot] c9251b3339
Bump org.projectlombok:lombok from 1.18.32 to 1.18.34 (#253)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.32 to 1.18.34.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.32...v1.18.34)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 23:34:31 -07:00
dependabot[bot] 987944188d
Bump org.apache.commons:commons-lang3 from 3.14.0 to 3.17.0 (#255)
Bumps org.apache.commons:commons-lang3 from 3.14.0 to 3.17.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-lang3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 22:06:51 -07:00
dependabot[bot] b0c9de6c52
Bump io.netty:netty-transport-native-kqueue (#247)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.109.Final to 4.1.112.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.109.Final...netty-4.1.112.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-08-16 14:17:53 -05:00
dependabot[bot] 7e891811aa
Bump com.nimbusds:nimbus-jose-jwt from 9.37.3 to 9.40 (#244)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.37.3 to 9.40.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/9.40..9.37.3)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-08-16 14:17:17 -05:00
dependabot[bot] 966b5b3f4f
Bump commons-cli:commons-cli from 1.7.0 to 1.8.0 (#241)
Bumps commons-cli:commons-cli from 1.7.0 to 1.8.0.

---
updated-dependencies:
- dependency-name: commons-cli:commons-cli
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-08-16 14:16:45 -05:00
dependabot[bot] b3d0f33e83
Bump docker/build-push-action from 5 to 6 (#246)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-08-16 14:16:16 -05:00
dependabot[bot] 46423ab3d7
Bump grpcVersion from 1.63.0 to 1.66.0 (#248)
Bumps `grpcVersion` from 1.63.0 to 1.66.0.

Updates `io.grpc:protoc-gen-grpc-java` from 1.63.0 to 1.66.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.63.0...v1.66.0)

Updates `io.grpc:grpc-protobuf` from 1.63.0 to 1.66.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.63.0...v1.66.0)

Updates `io.grpc:grpc-stub` from 1.63.0 to 1.66.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.63.0...v1.66.0)

Updates `io.grpc:grpc-inprocess` from 1.63.0 to 1.66.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.63.0...v1.66.0)

Updates `io.grpc:grpc-testing` from 1.63.0 to 1.66.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.63.0...v1.66.0)

Updates `io.grpc:grpc-netty-shaded` from 1.63.0 to 1.66.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.63.0...v1.66.0)

Updates `io.grpc:grpc-netty` from 1.63.0 to 1.66.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.63.0...v1.66.0)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-inprocess
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-08-16 14:13:40 -05:00
Max Lambrecht b0ab86ba31
Update gradle version to 8.10 (#231)
* Update gradle version to 8.7

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>

* Update gradle to 8.10

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>

---------

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
Co-authored-by: Ryan Turner <rturner3@users.noreply.github.com>
2024-08-16 12:01:51 -07:00
Max Lambrecht bf552c41d3
Change CI macos jdk distribution to zulu (#249)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-08-16 11:47:08 -07:00
dependabot[bot] 68565d8813
Bump commons-cli:commons-cli from 1.6.0 to 1.7.0 (#235)
Bumps commons-cli:commons-cli from 1.6.0 to 1.7.0.

---
updated-dependencies:
- dependency-name: commons-cli:commons-cli
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-18 17:30:23 -05:00
dependabot[bot] 7dfbffb8a3
Bump io.netty:netty-transport-native-kqueue (#234)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.108.Final to 4.1.109.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.108.Final...netty-4.1.109.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-15 16:24:38 -05:00
dependabot[bot] daa0bbae83
Bump grpcVersion from 1.62.2 to 1.63.0 (#232)
Bumps `grpcVersion` from 1.62.2 to 1.63.0.

Updates `io.grpc:protoc-gen-grpc-java` from 1.62.2 to 1.63.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.62.2...v1.63.0)

Updates `io.grpc:grpc-protobuf` from 1.62.2 to 1.63.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.62.2...v1.63.0)

Updates `io.grpc:grpc-stub` from 1.62.2 to 1.63.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.62.2...v1.63.0)

Updates `io.grpc:grpc-inprocess` from 1.62.2 to 1.63.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.62.2...v1.63.0)

Updates `io.grpc:grpc-testing` from 1.62.2 to 1.63.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.62.2...v1.63.0)

Updates `io.grpc:grpc-netty-shaded` from 1.62.2 to 1.63.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.62.2...v1.63.0)

Updates `io.grpc:grpc-netty` from 1.62.2 to 1.63.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.62.2...v1.63.0)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-inprocess
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 16:38:20 -05:00
dependabot[bot] 8af85e3f79
Bump io.netty:netty-transport-native-kqueue (#229)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.107.Final to 4.1.108.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.107.Final...netty-4.1.108.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-03-21 18:17:44 -05:00
dependabot[bot] 4cc83b9d80
Bump org.projectlombok:lombok from 1.18.30 to 1.18.32 (#228)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.30 to 1.18.32.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.30...v1.18.32)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 18:01:47 -05:00
Max Lambrecht 78f2b722a2
Add Run Build Workflow on Push (#226)
Add Run Build Workflow on Push

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-03-08 18:15:02 -08:00
Max Lambrecht 4649f50cf3
Update spire version used in integration tests to 1.9.1 (#225)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-03-08 12:20:17 -06:00
Max Lambrecht 7bbe3ed36b
Prepare Release 0.8.6 (#224)
Prepare Release 0.8.6

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-03-04 16:17:49 -06:00
Moritz Schmitz von Hülst 616e8bc3d1
Add functional test for java-spiffe-helper (#207)
Add functional test for java-spiffe-helper

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Only run on pull_request

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* No need for selector, there is only one pod

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Implement suggested changes

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Use tee for logs

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* A few changes according to comments

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Extend workflow to support multiple semver ranges for SPIRE helm charts

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Extend workflow to support multiple semver ranges for SPIRE helm charts

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* USe SemVer range instead of fixed version

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

---------

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-03-01 08:43:32 -06:00
Max Lambrecht c5629c58e7
Update PR Build Workflow (#217)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-28 15:31:02 -06:00
Max Lambrecht 803dea9a91
Automate and Refactor Release Build (#220)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-28 14:36:36 -06:00
dependabot[bot] f8a146b53e
Bump uk.org.webcompere:system-stubs-core from 2.0.2 to 2.1.6 (#191)
* Bump uk.org.webcompere:system-stubs-core from 2.0.2 to 2.1.6

Bumps [uk.org.webcompere:system-stubs-core](https://github.com/webcompere/system-stubs) from 2.0.2 to 2.1.6.
- [Release notes](https://github.com/webcompere/system-stubs/releases)
- [Changelog](https://github.com/webcompere/system-stubs/blob/main/History.md)
- [Commits](https://github.com/webcompere/system-stubs/compare/system-stubs-parent-2.0.2...system-stubs-parent-2.1.6)

---
updated-dependencies:
- dependency-name: uk.org.webcompere:system-stubs-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Conditional dependecy uk.org.webcompere version based on java version

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-28 14:30:03 -06:00
Max Lambrecht 4103c93262
Point badge to main branch (#219)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-28 12:48:39 -06:00
dependabot[bot] 6e993c4350
Bump grpcVersion from 1.61.1 to 1.62.2 (#222)
Bumps `grpcVersion` from 1.61.1 to 1.62.2.

Updates `io.grpc:protoc-gen-grpc-java` from 1.61.1 to 1.62.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.1...v1.62.2)

Updates `io.grpc:grpc-protobuf` from 1.61.1 to 1.62.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.1...v1.62.2)

Updates `io.grpc:grpc-stub` from 1.61.1 to 1.62.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.1...v1.62.2)

Updates `io.grpc:grpc-inprocess` from 1.61.1 to 1.62.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.1...v1.62.2)

Updates `io.grpc:grpc-testing` from 1.61.1 to 1.62.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.1...v1.62.2)

Updates `io.grpc:grpc-netty-shaded` from 1.61.1 to 1.62.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.1...v1.62.2)

Updates `io.grpc:grpc-netty` from 1.61.1 to 1.62.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.1...v1.62.2)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-inprocess
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-27 16:51:23 -06:00
dependabot[bot] 8c9a6d1333
Bump com.google.protobuf:protoc from 3.25.2 to 3.25.3 (#218)
Bumps [com.google.protobuf:protoc](https://github.com/protocolbuffers/protobuf) from 3.25.2 to 3.25.3.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.25.2...v3.25.3)

---
updated-dependencies:
- dependency-name: com.google.protobuf:protoc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-16 16:32:19 -06:00
Max Lambrecht 8bc61109db
Revert the addition of 'v' to docker image tags (#216)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-16 15:37:41 -06:00
Max Lambrecht 9215056b63
Fix Gradle Deprecation Warnings (#211)
Fix Gradle warnings

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-16 15:37:23 -06:00
Max Lambrecht 881dbd0bf8
Use Gradle Wrapper in Dockerfile (#213)
Use Gradle wrapper in Dockerfile

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-16 14:58:09 -06:00
Max Lambrecht 15d2980d74
Upgrade Gradle version to 8.6 (#214)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-16 14:57:35 -06:00
Max Lambrecht b19bd407b4
Fix jacoco config (#212)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-16 14:56:58 -06:00
Max Lambrecht bc833fef25
Update SPIRE version used in integration tests (#215)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-16 14:50:51 -06:00
Max Lambrecht 9b8b7dad87
Fix image url in the README (#209)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-15 17:09:16 -06:00
Max Lambrecht 0ff26093f4
Include 'v' in the image tag (#210)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-15 17:08:58 -06:00
dependabot[bot] 07285eb5e9
Bump io.netty:netty-transport-native-kqueue (#205)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.106.Final to 4.1.107.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.106.Final...netty-4.1.107.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-15 16:15:01 -06:00
Max Lambrecht 63a004d9f3
Prepare release 0.8.5 (#204)
Co-authored-by: Ryan Turner <turner@uber.com>
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-15 14:11:06 -06:00
Max Lambrecht 6a8e96ba96
Fix CODEOWNERS syntax (#208)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-15 13:22:48 -06:00
Max Lambrecht 4d374bf798
DefineC grpc-inprocess dependency as testImplementation (#206)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-15 13:20:55 -06:00
dependabot[bot] 93da062279
Bump jupiterVersion from 5.10.1 to 5.10.2 (#203)
Bumps `jupiterVersion` from 5.10.1 to 5.10.2.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.10.1 to 5.10.2
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.1...r5.10.2)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.10.1 to 5.10.2
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.1...r5.10.2)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.10.1 to 5.10.2
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.1...r5.10.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 14:57:01 -06:00
Moritz Schmitz von Hülst 27b2a01712
Add docker build for java-spiffe-helper container (#187)
* Add docker build for java-spiffe-helper container

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Adopt JDK has been deprecated in favor of temurin

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Push image after build

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Remove build on pull request and fix image tag

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Set user non-root, add separate stage for gradle dependencies and version to gradle properties

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Fix entrypoint

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Add example config and default container command

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Revert changes to gradle workflow and rename container build workflow

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Pin gradle builder image version

Co-authored-by: Ryan Turner <rturner3@users.noreply.github.com>
Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Add buildx action

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Use github variable in image tag

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Add Qemu

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Login before push

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Adopt JDK has been deprecated in favor of temurin

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Remove build on pull request and fix image tag

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Revert changes to gradle workflow and rename container build workflow

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Use new properties example file

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

* Minor improvements according to PR comments

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>

---------

Signed-off-by: Moritz Schmitz von Hülst <moritz.schmitz@oviva.com>
Co-authored-by: Ryan Turner <rturner3@users.noreply.github.com>
2024-02-05 15:51:08 -06:00
Max Lambrecht 666766a90f
Enhancements: Example Config File, Default Path, and Error Handling Improvements (#199)
Add default helper config
Improving java-spiffe-helper Runner and Config logic
Improve error handling in java-spiffe-helper
Update README

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-02-05 09:13:31 -06:00
dependabot[bot] 56bb734de3
Bump grpcVersion from 1.61.0 to 1.61.1 (#202)
Bumps `grpcVersion` from 1.61.0 to 1.61.1.

Updates `io.grpc:protoc-gen-grpc-java` from 1.61.0 to 1.61.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.0...v1.61.1)

Updates `io.grpc:grpc-protobuf` from 1.61.0 to 1.61.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.0...v1.61.1)

Updates `io.grpc:grpc-stub` from 1.61.0 to 1.61.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.0...v1.61.1)

Updates `io.grpc:grpc-inprocess` from 1.61.0 to 1.61.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.0...v1.61.1)

Updates `io.grpc:grpc-testing` from 1.61.0 to 1.61.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.0...v1.61.1)

Updates `io.grpc:grpc-netty-shaded` from 1.61.0 to 1.61.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.0...v1.61.1)

Updates `io.grpc:grpc-netty` from 1.61.0 to 1.61.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.61.0...v1.61.1)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-inprocess
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 09:07:06 -06:00
Ryan Turner 57c3634f06
Update to Gradle 8.5 (#201)
Signed-off-by: Ryan Turner <turner@uber.com>
2024-01-31 06:56:10 -06:00
dependabot[bot] 71eecdfefa
Bump commons-validator:commons-validator from 1.7 to 1.8.0 (#197)
Bumps commons-validator:commons-validator from 1.7 to 1.8.0.

---
updated-dependencies:
- dependency-name: commons-validator:commons-validator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-01-25 17:14:07 -06:00
dependabot[bot] 3730421a32
Bump commons-cli:commons-cli from 1.5.0 to 1.6.0 (#196)
Bumps commons-cli:commons-cli from 1.5.0 to 1.6.0.

---
updated-dependencies:
- dependency-name: commons-cli:commons-cli
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-01-25 17:13:49 -06:00
dependabot[bot] 2a97a32493
Bump jupiterVersion from 5.10.0 to 5.10.1 (#195)
Bumps `jupiterVersion` from 5.10.0 to 5.10.1.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.10.0 to 5.10.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.1)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.10.0 to 5.10.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.1)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.10.0 to 5.10.1
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.1)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-25 17:13:11 -06:00
Max Lambrecht 77f3893dd5
Add mergeServiceFiles to shawdowJar configs (#198)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-01-24 17:56:37 -06:00
dependabot[bot] c59c5d2c77
Bump com.google.protobuf:protoc from 3.23.4 to 3.25.2 (#193)
Bumps [com.google.protobuf:protoc](https://github.com/protocolbuffers/protobuf) from 3.23.4 to 3.25.2.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.23.4...v3.25.2)

---
updated-dependencies:
- dependency-name: com.google.protobuf:protoc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-01-24 09:31:04 -06:00
dependabot[bot] 9762799dcf
Bump grpcVersion from 1.58.0 to 1.61.0 (#190)
* Bump grpcVersion from 1.58.0 to 1.61.0

Bumps `grpcVersion` from 1.58.0 to 1.61.0.

Updates `io.grpc:protoc-gen-grpc-java` from 1.58.0 to 1.61.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.58.0...v1.61.0)

Updates `io.grpc:grpc-protobuf` from 1.58.0 to 1.61.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.58.0...v1.61.0)

Updates `io.grpc:grpc-stub` from 1.58.0 to 1.61.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.58.0...v1.61.0)

Updates `io.grpc:grpc-testing` from 1.58.0 to 1.61.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.58.0...v1.61.0)

Updates `io.grpc:grpc-netty-shaded` from 1.58.0 to 1.61.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.58.0...v1.61.0)

Updates `io.grpc:grpc-netty` from 1.58.0 to 1.61.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.58.0...v1.61.0)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Add missing dependency for grpc 1.61.0

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-01-24 09:25:38 -06:00
dependabot[bot] acdbd0696a
Bump io.netty:netty-transport-native-kqueue (#192)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.97.Final to 4.1.106.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.97.Final...netty-4.1.106.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-01-24 07:32:10 -06:00
dependabot[bot] 69628d2eb6
Bump org.apache.commons:commons-lang3 from 3.13.0 to 3.14.0 (#189)
Bumps org.apache.commons:commons-lang3 from 3.13.0 to 3.14.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-lang3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-24 07:24:28 -06:00
dependabot[bot] a073d66c5a
Bump actions/cache from 3 to 4 (#188)
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-22 13:58:35 -08:00
dependabot[bot] fec770ec95
Bump com.nimbusds:nimbus-jose-jwt from 9.35 to 9.37.3 (#184)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.35 to 9.37.3.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/9.37.3..9.35)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-01-22 06:52:09 -06:00
dependabot[bot] d318bc5b3c
Bump actions/setup-java from 3 to 4 (#182)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2024-01-22 06:50:32 -06:00
dependabot[bot] 84ad698571
Bump org.projectlombok:lombok from 1.18.28 to 1.18.30 (#170)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.28 to 1.18.30.
- [Release notes](https://github.com/projectlombok/lombok/releases)
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.28...v1.18.30)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 06:49:56 -06:00
dependabot[bot] c3b58d12c3
Bump com.nimbusds:nimbus-jose-jwt from 9.34 to 9.35 (#169)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.34 to 9.35.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/9.35..9.34)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 11:29:41 -05:00
dependabot[bot] 9a356a8757
Bump com.nimbusds:nimbus-jose-jwt from 9.31 to 9.34 (#168)
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.31 to 9.34.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/9.34..9.31)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2023-09-15 12:29:37 -05:00
dependabot[bot] a73726af97
Bump grpcVersion from 1.57.2 to 1.58.0 (#165)
Bumps `grpcVersion` from 1.57.2 to 1.58.0.

Updates `io.grpc:protoc-gen-grpc-java` from 1.57.2 to 1.58.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.57.2...v1.58.0)

Updates `io.grpc:grpc-protobuf` from 1.57.2 to 1.58.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.57.2...v1.58.0)

Updates `io.grpc:grpc-stub` from 1.57.2 to 1.58.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.57.2...v1.58.0)

Updates `io.grpc:grpc-testing` from 1.57.2 to 1.58.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.57.2...v1.58.0)

Updates `io.grpc:grpc-netty-shaded` from 1.57.2 to 1.58.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.57.2...v1.58.0)

Updates `io.grpc:grpc-netty` from 1.57.2 to 1.58.0
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.57.2...v1.58.0)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2023-09-15 12:23:48 -05:00
dependabot[bot] 99a03ff33d
Bump actions/checkout from 3 to 4 (#163)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 12:23:13 -05:00
dependabot[bot] 0c8b71642e
Bump io.netty:netty-transport-native-kqueue (#162)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.96.Final to 4.1.97.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.96.Final...netty-4.1.97.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-30 13:21:38 -05:00
dependabot[bot] 817709c82f
Bump grpcVersion from 1.56.1 to 1.57.2 (#161)
Bumps `grpcVersion` from 1.56.1 to 1.57.2.

Updates `io.grpc:protoc-gen-grpc-java` from 1.56.1 to 1.57.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2)

Updates `io.grpc:grpc-protobuf` from 1.56.1 to 1.57.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2)

Updates `io.grpc:grpc-stub` from 1.56.1 to 1.57.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2)

Updates `io.grpc:grpc-testing` from 1.56.1 to 1.57.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2)

Updates `io.grpc:grpc-netty-shaded` from 1.56.1 to 1.57.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2)

Updates `io.grpc:grpc-netty` from 1.56.1 to 1.57.2
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2023-08-17 09:51:03 -05:00
dependabot[bot] c9f01b09bf
Bump io.netty:netty-transport-native-kqueue (#156)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.94.Final to 4.1.96.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.94.Final...netty-4.1.96.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-17 09:44:49 -05:00
dependabot[bot] 0246dfb224
Bump jupiterVersion from 5.9.3 to 5.10.0 (#155)
Bumps `jupiterVersion` from 5.9.3 to 5.10.0.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.9.3 to 5.10.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.3...r5.10.0)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.9.3 to 5.10.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.3...r5.10.0)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.9.3 to 5.10.0
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.3...r5.10.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-31 12:22:24 -07:00
dependabot[bot] 659145cc3e
Bump org.apache.commons:commons-lang3 from 3.12.0 to 3.13.0 (#158)
Bumps org.apache.commons:commons-lang3 from 3.12.0 to 3.13.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-lang3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-31 11:53:38 -07:00
dependabot[bot] ad06ea59af
Bump com.google.protobuf:protobuf-gradle-plugin from 0.9.3 to 0.9.4 (#153)
Bumps [com.google.protobuf:protobuf-gradle-plugin](https://github.com/google/protobuf-gradle-plugin) from 0.9.3 to 0.9.4.
- [Release notes](https://github.com/google/protobuf-gradle-plugin/releases)
- [Commits](https://github.com/google/protobuf-gradle-plugin/commits)

---
updated-dependencies:
- dependency-name: com.google.protobuf:protobuf-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-31 11:48:10 -07:00
dependabot[bot] e4e7f8dace
Bump com.google.protobuf:protoc from 3.23.1 to 3.23.4 (#152)
Bumps [com.google.protobuf:protoc](https://github.com/protocolbuffers/protobuf) from 3.23.1 to 3.23.4.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.23.1...v3.23.4)

---
updated-dependencies:
- dependency-name: com.google.protobuf:protoc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2023-07-11 15:21:46 -05:00
dependabot[bot] 3f9e328936
Bump grpcVersion from 1.55.1 to 1.56.1 (#151)
Bumps `grpcVersion` from 1.55.1 to 1.56.1.

Updates `io.grpc:protoc-gen-grpc-java` from 1.55.1 to 1.56.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.55.1...v1.56.1)

Updates `io.grpc:grpc-protobuf` from 1.55.1 to 1.56.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.55.1...v1.56.1)

Updates `io.grpc:grpc-stub` from 1.55.1 to 1.56.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.55.1...v1.56.1)

Updates `io.grpc:grpc-testing` from 1.55.1 to 1.56.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.55.1...v1.56.1)

Updates `io.grpc:grpc-netty-shaded` from 1.55.1 to 1.56.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.55.1...v1.56.1)

Updates `io.grpc:grpc-netty` from 1.55.1 to 1.56.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.55.1...v1.56.1)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <maxlambrecht@gmail.com>
2023-07-11 15:17:27 -05:00
dependabot[bot] bc7f6703fa
Bump io.netty:netty-transport-native-kqueue (#150)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.92.Final to 4.1.94.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.92.Final...netty-4.1.94.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-11 15:12:11 -05:00
dependabot[bot] 67f0174b11
Bump org.projectlombok:lombok from 1.18.26 to 1.18.28 (#145)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.26 to 1.18.28.
- [Release notes](https://github.com/projectlombok/lombok/releases)
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.26...v1.18.28)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-24 21:42:02 -05:00
dependabot[bot] d18fa737d0
Bump com.google.protobuf:protoc from 3.23.0 to 3.23.1 (#144)
Bumps [com.google.protobuf:protoc](https://github.com/protocolbuffers/protobuf) from 3.23.0 to 3.23.1.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.23.0...v3.23.1)

---
updated-dependencies:
- dependency-name: com.google.protobuf:protoc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-17 18:10:22 -05:00
dependabot[bot] b925e5ecf1
Bump com.google.protobuf:protoc from 3.21.12 to 3.23.0 (#142)
Bumps [com.google.protobuf:protoc](https://github.com/protocolbuffers/protobuf) from 3.21.12 to 3.23.0.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.21.12...v3.23.0)

---
updated-dependencies:
- dependency-name: com.google.protobuf:protoc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-10 10:37:48 -05:00
dependabot[bot] 67a34e986c
Bump grpcVersion from 1.54.1 to 1.55.1 (#143)
Bumps `grpcVersion` from 1.54.1 to 1.55.1.

Updates `io.grpc:protoc-gen-grpc-java` from 1.54.1 to 1.55.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1)

Updates `io.grpc:grpc-protobuf` from 1.54.1 to 1.55.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1)

Updates `io.grpc:grpc-stub` from 1.54.1 to 1.55.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1)

Updates `io.grpc:grpc-testing` from 1.54.1 to 1.55.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1)

Updates `io.grpc:grpc-netty-shaded` from 1.54.1 to 1.55.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1)

Updates `io.grpc:grpc-netty` from 1.54.1 to 1.55.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-10 10:33:40 -05:00
dependabot[bot] 3e1a50e5f8
Bump com.google.protobuf:protobuf-gradle-plugin from 0.9.2 to 0.9.3 (#141)
Bumps [com.google.protobuf:protobuf-gradle-plugin](https://github.com/google/protobuf-gradle-plugin) from 0.9.2 to 0.9.3.
- [Release notes](https://github.com/google/protobuf-gradle-plugin/releases)
- [Commits](https://github.com/google/protobuf-gradle-plugin/compare/0.9.2...v0.9.3)

---
updated-dependencies:
- dependency-name: com.google.protobuf:protobuf-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 11:54:28 -05:00
Max Lambrecht 73404a80ad
Upgrade gradle version to 8.1.1 (#138)
* Update Gradle version to 8.1.1

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2023-04-27 11:36:01 -07:00
dependabot[bot] a0480f91d9
Bump jupiterVersion from 5.9.2 to 5.9.3 (#140)
Bumps `jupiterVersion` from 5.9.2 to 5.9.3.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.9.2 to 5.9.3
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.2...r5.9.3)

Updates `org.junit.jupiter:junit-jupiter-engine` from 5.9.2 to 5.9.3
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.2...r5.9.3)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.9.2 to 5.9.3
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.2...r5.9.3)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-27 13:25:37 -05:00
dependabot[bot] f3d360ba7d
Bump io.netty:netty-transport-native-kqueue (#139)
Bumps [io.netty:netty-transport-native-kqueue](https://github.com/netty/netty) from 4.1.91.Final to 4.1.92.Final.
- [Release notes](https://github.com/netty/netty/releases)
- [Commits](https://github.com/netty/netty/compare/netty-4.1.91.Final...netty-4.1.92.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport-native-kqueue
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-27 13:24:57 -05:00
dependabot[bot] cb9baa85c6
Bump grpcVersion from 1.54.0 to 1.54.1 (#136)
Bumps `grpcVersion` from 1.54.0 to 1.54.1.

Updates `io.grpc:protoc-gen-grpc-java` from 1.54.0 to 1.54.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1)

Updates `io.grpc:grpc-protobuf` from 1.54.0 to 1.54.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1)

Updates `io.grpc:grpc-stub` from 1.54.0 to 1.54.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1)

Updates `io.grpc:grpc-testing` from 1.54.0 to 1.54.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1)

Updates `io.grpc:grpc-netty-shaded` from 1.54.0 to 1.54.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1)

Updates `io.grpc:grpc-netty` from 1.54.0 to 1.54.1
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1)

---
updated-dependencies:
- dependency-name: io.grpc:protoc-gen-grpc-java
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-stub
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-testing
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-netty-shaded
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.grpc:grpc-netty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-18 09:23:21 -05:00
Max Lambrecht 537e7f7a7b
Prepare release 0.8.4 (#134)
Prepare release 0.8.4

Co-authored-by: Ryan Turner <rturner3@users.noreply.github.com>
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-14 13:47:26 -05:00
dependabot[bot] 70ccecd600
Bump mockitoVersion from 3.12.4 to 4.11.0 (#133)
* Bump mockitoVersion from 3.12.4 to 4.11.0

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-14 09:52:15 -05:00
dependabot[bot] 5e9ead9efd
Bump com.github.kt3k.coveralls from 2.12.0 to 2.12.2 (#132)
Bumps com.github.kt3k.coveralls from 2.12.0 to 2.12.2.

---
updated-dependencies:
- dependency-name: com.github.kt3k.coveralls
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-13 17:14:08 -05:00
dependabot[bot] 7fc2406087
Bump org.bouncycastle:bcpkix-jdk15on from 1.66 to 1.70 (#131)
Bumps [org.bouncycastle:bcpkix-jdk15on](https://github.com/bcgit/bc-java) from 1.66 to 1.70.
- [Release notes](https://github.com/bcgit/bc-java/releases)
- [Changelog](https://github.com/bcgit/bc-java/blob/master/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

---
updated-dependencies:
- dependency-name: org.bouncycastle:bcpkix-jdk15on
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 17:12:53 -05:00
dependabot[bot] 39b8c14650
Bump com.google.osdetector from 1.6.2 to 1.7.3 (#125)
Bumps com.google.osdetector from 1.6.2 to 1.7.3.

---
updated-dependencies:
- dependency-name: com.google.osdetector
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-13 16:46:25 -05:00
dependabot[bot] 6fe6a6eee2
Bump commons-cli:commons-cli from 1.4 to 1.5.0 (#124)
Bumps commons-cli:commons-cli from 1.4 to 1.5.0.

---
updated-dependencies:
- dependency-name: commons-cli:commons-cli
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-13 16:46:07 -05:00
dependabot[bot] d14977c7ec
Bump org.projectlombok:lombok from 1.18.20 to 1.18.26 (#128)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.20 to 1.18.26.
- [Release notes](https://github.com/projectlombok/lombok/releases)
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.20...v1.18.26)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-13 16:45:32 -05:00
dependabot[bot] 72f352fc30
Bump org.apache.commons:commons-lang3 from 3.11 to 3.12.0 (#129)
Bumps org.apache.commons:commons-lang3 from 3.11 to 3.12.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-lang3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-13 16:42:10 -05:00
dependabot[bot] 719554eab3
Bump actions/setup-java from 2 to 3 (#127)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 2 to 3.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-13 16:39:12 -05:00
dependabot[bot] ac7ec7da66
Bump actions/cache from 2 to 3 (#126)
Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 16:35:47 -05:00
dependabot[bot] f949feb2d9
Bump actions/checkout from 2 to 3 (#123)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 16:33:53 -05:00
Max Lambrecht 479943d510
Create .github/dependabot.yml (#122)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-13 16:25:47 -05:00
Max Lambrecht 47edb81ae3
Prepare release 0.8.3 (#121)
Bump library version to 0.8.3
Add CHANGELOG.md

Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-12 15:51:58 -05:00
Max Lambrecht e5600c3f4d
Update gradle version to 8.0.2 (#104)
Update gradle version to 8.0.2

Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-12 13:16:22 -05:00
Guilherme Carvalho 0d0aae967f
Add SVID hints on workload api client (#114)
Add SVID hints on workload api client

Signed-off-by: Guilherme Carvalho <guilhermocc@proton.me>
2023-04-12 13:10:44 -05:00
Max Lambrecht c3dc4950e5
Fix CachedJwtSource tests (#120)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-10 15:02:27 -05:00
Max Lambrecht 50208a709a
Update grpc dependency (#117)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-05 16:40:47 -05:00
Max Lambrecht 11d0180134
Update netty dependency (#118)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-05 16:40:08 -05:00
Max Lambrecht 8c2d990e3f
Update nimbus-jose-jwt dependency (#119)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-05 16:33:26 -05:00
Max Lambrecht 3ca77c1de2
Add `CachedJwtSource` (#116)
Add CachedJwtSource

Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-04-04 13:49:32 -05:00
Max Lambrecht 5e16f7a632
Bump library version to 0.8.2 (#113)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-03-07 14:13:32 -06:00
Max Lambrecht f9dc354ae4
Solve memory leak. (#112)
Reverting changes that introduced a memory leak. Addressing error when context was cancelled

Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-03-07 13:30:51 -06:00
Max Lambrecht 942bcc9eb4
Add default constructor to SpiffeSslSocketFactory (#109)
Add default constructor to SpiffeSslSocketFactory

Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-02-16 16:13:21 -03:00
Max Lambrecht d9b932f782
Bump library to 0.8.1. Update README (#107)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-01-11 16:26:50 -03:00
Max Lambrecht 34e9b6c5fe
Updating Nimbus dependency version to 9.28 (#103)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-01-11 15:36:14 -03:00
Max Lambrecht c1c9be0c2e
Update SPIRE version for integration tests to 1.5.3 (#105)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-01-11 15:26:40 -03:00
Max Lambrecht 48991a0105
Fix close method in WorkloadApiClient (#102)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-01-11 15:00:07 -03:00
Thomas Richner 0085ce08f9
Aarch64 support (#101)
Add aarch64 module for OSX

Signed-off-by: Thomas Richner <thomas.richner@oviva.com>
Co-authored-by: Max Lambrecht <max.lambrecht@hpe.com>
2023-01-11 14:37:29 -03:00
Max Lambrecht a878d7d7f4
Update grpc-java dependency to 1.49.0 (#97)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2022-08-24 13:38:55 -07:00
Max Lambrecht 3ec46b414d
Update Nimbus JOSE-JWT dependency to 1.24.2 (#98)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2022-08-24 13:33:14 -07:00
Max Lambrecht 6eb2ed5c0f
Update version of SPIRE used in integration tests (#99)
Signed-off-by: Max Lambrecht <max.lambrecht@hpe.com>
2022-08-24 12:59:57 -07:00
Max Lambrecht 5840d8ff4e
Update README release 0.8.0 (#95) 2022-05-06 17:22:25 -03:00
Max Lambrecht 8120649796
Bump library version for release 0.8.0 (#94)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2022-05-06 16:30:07 -03:00
Max Lambrecht 4948b2bf2e
Update Gradle version to 7.4.2 (#93)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2022-04-29 14:18:03 -07:00
Max Lambrecht c0a4ddc089
Update Nimbus Jose-Jwt dependency to 9.22 (#92)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2022-04-29 14:08:47 -07:00
Max Lambrecht df4e0b3f71
Update grpc dependency to 1.46.0 (#91)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2022-04-29 14:06:02 -07:00
M1a0 6cdc17eb9c
Add FetchJWTSVIDs function for workloadapi and jwtSource (#90)
Signed-off-by: Yuhan Li <liyuhan.loveyana@bytedance.com>
2022-04-27 18:21:24 -03:00
Max Lambrecht 5a9fa55fe6
Update Nimbus JWT-JOSE dependency. (#89)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2022-02-07 17:47:36 -03:00
Max Lambrecht 0de5d0685b
Update grpc dependency. (#88)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2022-02-07 17:47:19 -03:00
Max Lambrecht f4e9f582fd
Update spire version in integration tests. (#87)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2022-02-07 17:47:02 -03:00
Max Lambrecht 66213fa047
Update gradle version. (#86)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2022-02-07 17:46:39 -03:00
Max Lambrecht cac2f603c4
Update artifact versions in READMEs (#85)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-09-08 15:23:00 -03:00
Max Lambrecht 77148473c4
Strict SPIFFE ID parsing (#74)
Strict SPIFFE ID parsing.

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-09-08 14:03:24 -03:00
Max Lambrecht 5289d4bb98
Update integration tests SPIRE version (#84)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-09-07 11:48:49 -07:00
Max Lambrecht 96fa2db3de
Update Nimbus JWT-JOSE dependency (#82)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-09-07 11:48:14 -07:00
Max Lambrecht 87420066c7
Update grpc-java dependency (#81)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-09-07 11:10:49 -07:00
Ian Haken 744f5276b4
Add support to the SpiffeTrustManager for a SpiffeIdVerifier callback. (#77)
Add support to the SpiffeTrustManager for a SpiffeIdVerifier callback.

Signed-off-by: Ian Haken <ihaken@netflix.com>
2021-08-14 08:10:06 -03:00
Max Lambrecht 7edf338ac1
Make CI script use SPIRE 1.0.1 (#78)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-08-11 20:03:05 -07:00
Max Lambrecht 7657acfafd
Add Integration tests for Workload API client. (#75)
* Add integration tests and setup script.

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
Co-authored-by: Ryan Turner <rturner3@users.noreply.github.com>
2021-07-27 10:08:32 -07:00
Ryan Turner 7e08d92859
Merge pull request #73 from maxlambrecht/add-github-actions
Configure Github Actions
2021-06-23 18:41:45 -07:00
Max Lambrecht 6a08cc3a60 Resolve nits.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-23 09:29:54 -03:00
Max Lambrecht 906995fd81 Refactor Github Actions config.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 13:37:41 -03:00
Max Lambrecht 1161be29a4 Remove unnecessary build.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 13:29:04 -03:00
Max Lambrecht 028302358d Remove CI name.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 13:14:15 -03:00
Max Lambrecht f01ee2cee7 Set coveralls CI name.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 12:52:23 -03:00
Max Lambrecht c40da6a305 Set coveralls CI name.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 12:47:33 -03:00
Max Lambrecht 30d751a579 Update secret name.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 12:37:24 -03:00
Max Lambrecht ffcb63309b Add build to coverage step.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 12:25:08 -03:00
Max Lambrecht c9c6aeae5a Add env variable to run coverage plugin.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 12:12:51 -03:00
Max Lambrecht 4109b2bdd4 Update coveralls gradle plugin.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 11:55:03 -03:00
Max Lambrecht 98153dd988 Fix Github Action config.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 11:42:18 -03:00
Max Lambrecht 7e298d72b1 Add OS/JDK matrix to Github Actions config.
Update build badge link.

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 11:40:04 -03:00
Max Lambrecht c010b90c00 Add OS/JDK matrix to Github Actions config.
Update build badge link.

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-22 11:36:18 -03:00
Max Lambrecht 817b1102a5 Add Github Actions.
Upgrade gradle to 7.1.
Update Lombok dependency.

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-06-19 15:55:27 -03:00
Ryan Turner 7a994861ef
Merge pull request #72 from maxlambrecht/fix/update-proto
Update workload.proto with the lastest version from the SPIFFE repo.
2021-05-27 19:09:52 -07:00
Max Lambrecht 377225ac7b Update workload.proto with the lastest version from the SPIFFE repo.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-05-27 15:55:07 -03:00
Ryan Turner 060bec6f33
Merge pull request #70 from maxlambrecht/nimbus-dependency-update
Update JWT-JOSE dependency
2021-04-12 12:41:53 -07:00
Ryan Turner 1f53be1c6c
Merge branch 'master' into nimbus-dependency-update 2021-04-12 12:40:45 -07:00
Ryan Turner 16d02d65ba
Merge pull request #69 from maxlambrecht/netty-dependency-update
Update netty-transport-native-kqueue dependecy to 4.1.63
2021-04-12 12:40:01 -07:00
Ryan Turner 86f9546178
Merge branch 'master' into netty-dependency-update 2021-04-12 12:39:56 -07:00
Ryan Turner 2950899913
Merge pull request #68 from maxlambrecht/grpc-dependency-update
Update grpc-java dependency to 1.37.0
2021-04-12 12:39:24 -07:00
Max Lambrecht aec49499c5 Update JWT-JOSE dependency to 9.8.1
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-04-12 15:54:12 -03:00
Max Lambrecht 467b1873cd Update netty-transport-native-kqueue dependecy to 4.1.63
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-04-12 15:48:00 -03:00
Max Lambrecht b8f936e97a Update grpc-java dependency to 1.37.0
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-04-12 14:57:41 -03:00
Max Lambrecht 8c2cbbc3d5
Update library version to 0.7.0 (#66)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-03-15 17:22:11 -03:00
Ryan Turner 1177178a1e
Merge pull request #63 from maxlambrecht/fetch-x509-bundles
Implement FetchX509Bundles method on WorkloadAPI client
2021-03-15 12:49:32 -07:00
Max Lambrecht ede1878fd5 Addressing PR comments
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-03-15 09:59:15 -03:00
Max Lambrecht baa447b058 Add FetchX509Bundles method to Workload API client
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-03-08 18:44:21 -03:00
Max Lambrecht 9a6e805756
Update JWT-JOSE Nimbus dependency to 9.7 (#65)
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>

Co-authored-by: Ryan Turner <rturner3@users.noreply.github.com>
2021-03-08 18:41:57 -03:00
Ryan Turner 600c098d10
Merge pull request #64 from maxlambrecht/grpc-dependency-update
Update grpc-java dependency to 1.36.0
2021-03-08 10:04:07 -08:00
Max Lambrecht 41153174f9 Update grpc-java dependency to 1.36.0
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-03-08 13:25:46 -03:00
Max Lambrecht 0ee9ae28fa
Validate JWT 'typ' header. (#62)
* Validate JWT 'typ' header.

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-02-08 16:05:36 -03:00
Ryan Turner e33417b10b
Merge pull request #60 from maxlambrecht/validate-jwt-alg
Validate 'alg' header when parsing JWT-SVIDs from tokens
2021-02-05 10:11:02 -08:00
Max Lambrecht de82ebf916 Minor exception messages improvements.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-02-05 10:37:44 -03:00
Max Lambrecht f8970af7f4 Validate 'alg' header when parsing JWT-SVIDs from tokens.
Some minor refactors and improvements.

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-02-04 16:30:41 -03:00
Ryan Turner c285da249e
Merge pull request #57 from maxlambrecht/master
Upgrade grpc dependency to 1.35.0
2021-02-03 10:12:03 -08:00
Max Lambrecht 1b70bbf1d5 Upgrade grpc dependency to 1.35.0
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-02-03 11:47:08 -03:00
Ryan Turner 6551f6d131
Merge pull request #56 from maxlambrecht/master
Remove private/public key match verification.
2021-02-02 17:45:54 -08:00
Max Lambrecht 3049810a35 Remove private/public key match verification.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-02-02 21:06:55 -03:00
Max Lambrecht 45e3fc86a6
Merge pull request #54 from rturner3/java-spiffe-provider-doc-fix
Remove separator argument from toSetOfSpiffeIds() examples
2021-01-13 09:18:53 -03:00
Ryan Turner f7ffdea3b1 Remove separator argument from toSetOfSpiffeIds() examples
The ',' separator is not accepted as valid by the library. There is now
an override method which uses a default separator, so prefer that for
the examples, where the separator character is not particularly
relevant.

Signed-off-by: Ryan Turner <turner@uber.com>
2021-01-12 20:31:26 -08:00
Ryan Turner df9dd31685
Merge pull request #53 from maxlambrecht/add-missing-spiffeid-validations
Make SPIFFE ID and TrustDomain compliant with the specs
2021-01-06 12:09:46 -08:00
Max Lambrecht bd6ca04270 Addressing PR comments.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-01-06 10:02:46 -03:00
Ryan Turner 0053f22ee4
Merge pull request #51 from maxlambrecht/gradle-upgrade
Gradle upgrade to 6.7.1
2021-01-05 10:54:48 -08:00
Max Lambrecht 9757e62ad3 Add missing SPIFFE ID and Trust Domain maximum length validations.
Fix parse SPIFFE ID to preserve path case.
Refactoring tests.

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2021-01-05 12:46:41 -03:00
Max Lambrecht e37b9b50e9 Upgrade gradle wrapper to 6.7.1
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-11-25 09:26:27 -03:00
Max Lambrecht accb81c8d6
Merge pull request #49 from maxlambrecht/master
Use google osdetector plugin instead of internal gradle class.
2020-11-24 15:11:10 -03:00
Max Lambrecht e7425fbd4e Revert gradle wrapper update.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-11-24 15:05:12 -03:00
Max Lambrecht 48e72f0412
Merge pull request #50 from rturner3/fix-readme
Fix typo in README.md
2020-11-24 15:00:27 -03:00
Ryan Turner a1757c5557 Fix typo in README.md
Signed-off-by: Ryan Turner <turner@uber.com>
2020-11-24 09:41:12 -08:00
Max Lambrecht aff23a7d79 Use google osdetector plugin instead of internal gradle class.
Upgrade gradle wrapper to 6.7.1

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-11-24 10:57:37 -03:00
Max Lambrecht 20d0efead2
Merge pull request #47 from srwaggon/patch-2
Update README.md
2020-11-14 10:16:21 -03:00
Max Lambrecht c04dd2163f
Merge pull request #46 from srwaggon/patch-1
Update README.md
2020-11-14 10:16:03 -03:00
Samuel Waggoner 1ff550ac43
Update README.md
Update example to match current code contract
2020-11-13 16:05:39 -08:00
Samuel Waggoner c3d118da45
Update README.md
Amend parent class in example
2020-11-13 16:01:53 -08:00
Max Lambrecht b67a2f1bd8
Merge pull request #45 from maxlambrecht/master
Add maintainer to CODEOWNERS file
2020-11-10 08:37:15 -03:00
Max Lambrecht 437e0b5469 Add maintainer to CODEOWNERS file
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-11-09 17:42:15 -03:00
Max Lambrecht aba2c2d272
Merge pull request #44 from maxlambrecht/bump-0.6.3
Bump to 0.6.3
2020-10-27 18:24:19 -03:00
Max Lambrecht 2f295c86ab
Merge pull request #43 from maxlambrecht/docs-improvements
Minor improvements in READMEs and javadocs.
2020-10-27 18:23:07 -03:00
Max Lambrecht ebb933557c
Merge pull request #42 from maxlambrecht/update-lombok-dependency
Updating lombok and test libraries dependencies.
2020-10-27 18:22:45 -03:00
Max Lambrecht 00c3407c33 Merge branch 'update-lombok-dependency' of https://github.com/maxlambrecht/java-spiffe into update-lombok-dependency 2020-10-27 18:21:45 -03:00
Max Lambrecht 2d3b57ad28 Updating lombok and test libraries dependencies.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-10-27 18:20:00 -03:00
Max Lambrecht c08462accf Merge remote-tracking branch 'upstream/master' into master 2020-10-27 18:17:29 -03:00
Max Lambrecht 815fb40cf4
Merge pull request #41 from maxlambrecht/nimbus-dependency-update
Updating nimbus jwt-jose dependency.
2020-10-27 18:15:43 -03:00
Max Lambrecht 4e53c3ec71
Merge pull request #40 from maxlambrecht/grpc-dependency-update
Updating gRPC and protobuf dependencies.
2020-10-27 18:15:08 -03:00
Max Lambrecht b9035b4dac Bump to 0.6.3
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-10-27 10:57:39 -03:00
Max Lambrecht dbd44a4db4 Minor improvements in READMEs and javadocs.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-10-27 10:52:22 -03:00
Max Lambrecht ddff049b5d Updating lombok and test libraries dependencies.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-10-27 10:32:38 -03:00
Max Lambrecht 56e22854f3 Updating nimbus jwt dependency.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-10-27 10:21:53 -03:00
Max Lambrecht 3cfa6a8ccd Updating gRPC and protobuf dependencies.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-10-27 09:34:08 -03:00
Max Lambrecht b94d8da155
Merge pull request #38 from maxlambrecht/master
Adding a note about java-spiffe-helper artifact
2020-10-02 11:30:56 -03:00
Max Lambrecht 057a048397 Add note to README to clarify that java-spiffe-helper artifact is published to Github releases.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-10-02 11:17:15 -03:00
105 changed files with 5033 additions and 1387 deletions

110
.dockerignore Normal file
View File

@ -0,0 +1,110 @@
### Java template
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# GitHub
.github
# Git
.git

View File

@ -0,0 +1,49 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: java-spiffe-helper
data:
java-spiffe-helper.properties: |
keyStorePath=/tmp/keystore.p12
keyStorePass=password
keyPass=password
trustStorePath=/tmp/truststore.p12
trustStorePass=password
keyStoreType=pkcs12
keyAlias=spiffe
spiffeSocketPath=unix:/run/spire/agent-sockets/spire-agent.sock
---
apiVersion: v1
kind: Pod
metadata:
name: java-spiffe-helper
labels:
app: java-spiffe-helper
spec:
containers:
- name: java-spiffe-helper
image: java-spiffe-helper:test
imagePullPolicy: IfNotPresent
readinessProbe:
initialDelaySeconds: 15
exec:
command:
- ls
- /tmp/truststore.p12
volumeMounts:
- name: properties
mountPath: /app/java-spiffe-helper.properties
subPath: java-spiffe-helper.properties
- name: spire-sockets
mountPath: /run/spire/agent-sockets
readOnly: true
restartPolicy: Never
volumes:
- name: properties
configMap:
name: java-spiffe-helper
- name: spire-sockets
hostPath:
path: /run/spire/agent-sockets
type: DirectoryOrCreate

View File

@ -0,0 +1,18 @@
spire-server:
ca_subject:
common_name: common_name
controllerManager:
identities:
clusterSPIFFEIDs:
default:
enabled: false
java-spiffe-helper:
spiffeIDTemplate: spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: default
podSelector:
matchLabels:
app: java-spiffe-helper
dnsNameTemplates:
- dnsNameTemplate

15
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,15 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

79
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,79 @@
name: Build
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
build-and-test-on-linux:
runs-on: ubuntu-latest
strategy:
matrix:
java-version: [ 8, 11, 17, 21 ]
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java-version }}
distribution: 'adopt'
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build with Gradle and generate the artifacts (also run the tests)
run: ./gradlew build
- name: Run integration tests
run: ./.github/workflows/scripts/integration-tests.sh
- name: Cleanup Gradle Cache
# Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
# Restoring these files from a GitHub Actions cache might cause problems for future builds.
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties
build-and-test-on-macos:
runs-on: macos-latest
strategy:
matrix:
java-version: [ 8, 11, 17, 21 ]
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java-version }}
distribution: 'zulu'
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build with Gradle and generate the artifacts
run: ./gradlew build
- name: Cleanup Gradle Cache
# Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
# Restoring these files from a GitHub Actions cache might cause problems for future builds.
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties

28
.github/workflows/coverage.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: coverage
on:
push:
branches: [ main ]
jobs:
create-coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '16'
distribution: 'adopt'
- name: Generate and upload coverage report
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
run: ./gradlew jacocoTestReport coveralls
- name: Cleanup Gradle Cache
# Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
# Restoring these files from a GitHub Actions cache might cause problems for future builds.
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties

View File

@ -0,0 +1,85 @@
name: Java SPIFFE Helper CI
on:
- pull_request
jobs:
test:
name: Test with SPIRE helm chart in version ${{ matrix.spire-chart-version.spire }}
runs-on: ubuntu-latest
strategy:
matrix:
spire-chart-version:
- spire: '0.17.x'
crds: '0.3.x'
env:
HELM_REPOSITORY: https://spiffe.github.io/helm-charts-hardened/
KEYSTORE_COMMON_NAME: keystore-${{ github.sha }}
TRUSTSTORE_COMMON_NAME: truststore-${{ github.sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
- name: Setup buildx
uses: docker/setup-buildx-action@v3
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Build container
uses: docker/build-push-action@v6
with:
context: .
tags: java-spiffe-helper:test
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Create local kubernetes cluster
uses: helm/kind-action@v1
with:
cluster_name: kind
- name: Load container image onto kubernetes node
run: kind load docker-image java-spiffe-helper:test --name kind
- name: Install SPIRE CRDs in version ${{ matrix.spire-chart-version.crds }}
run: |
helm upgrade --install -n spire-server spire-crds spire-crds \
--repo ${{ env.HELM_REPOSITORY }} \
--version ${{ matrix.spire-chart-version.crds }} \
--create-namespace
- name: Install SPIRE server in version ${{ matrix.spire-chart-version.spire }} and set to-be-verified values for common name
run: |
helm upgrade --install -n spire-server spire spire \
--repo ${{ env.HELM_REPOSITORY }} \
--version ${{ matrix.spire-chart-version.spire }} \
--values .github/ci-k8s-configs/spire-values.yaml \
--set spire-server.ca_subject.common_name="$TRUSTSTORE_COMMON_NAME" \
--set spire-server.controllerManager.identities.clusterSPIFFEIDs.java-spiffe-helper.dnsNameTemplates[0]="$KEYSTORE_COMMON_NAME"
- name: Deploy java-spiffe-helper pod to local cluster
run: kubectl apply -f .github/ci-k8s-configs/java-spiffe-helper.yaml
- name: Wait for java-spiffe-helper pod to become ready
run: kubectl wait pod/java-spiffe-helper --for condition=Ready --timeout=90s
- name: Output logs of java-spiffe-helper pod
if: ${{ failure() }}
run: kubectl logs pod/java-spiffe-helper
- name: Describe java-spiffe-helper pod
if: ${{ failure() }}
run: kubectl describe pod/java-spiffe-helper
- name: Copy keystore from java-spiffe-helper pod
run: kubectl cp java-spiffe-helper:/tmp/keystore.p12 keystore.p12
- name: Copy truststore from java-spiffe-helper pod
run: kubectl cp java-spiffe-helper:/tmp/truststore.p12 truststore.p12
- name: Verify keystore contains configured common name
run: keytool -v -list -keystore keystore.p12 -storepass password | grep "CN=${{ env.KEYSTORE_COMMON_NAME }}"
- name: Output keystore contents
if: ${{ failure() }}
run: keytool -v -list -keystore keystore.p12 -storepass password
- name: Verify truststore contains configured common name
run: keytool -v -list -keystore truststore.p12 -storepass password | grep "CN=${{ env.TRUSTSTORE_COMMON_NAME }}"
- name: Output truststore contents
if: ${{ failure() }}
run: keytool -v -list -keystore truststore.p12 -storepass password

59
.github/workflows/release_build.yml vendored Normal file
View File

@ -0,0 +1,59 @@
name: Release Build
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
env:
REGISTRY: ghcr.io
JAVA_VERSION: '17'
jobs:
publishToMaven:
runs-on: ubuntu-latest
env:
NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }}
NEXUS_TOKEN: ${{ secrets.NEXUS_TOKEN }}
PGP_PRIVATE_KEY: ${{ secrets.PGP_PRIVATE_KEY }}
PGP_KEY_PASSPHRASE: ${{ secrets.PGP_KEY_PASSPHRASE }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: 'adopt'
- name: Publish to Nexus Maven Repository
run: ./gradlew publish
publishDockerImage:
needs: publishToMaven
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Determine Docker Tag
run: echo "DOCKER_TAG=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
- name: Publish java-spiffe-helper Docker Image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.REGISTRY }}/${{ github.repository }}-helper:${{ env.DOCKER_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -0,0 +1,77 @@
#!/usr/bin/env bash
# Start a SPIRE Server and Agent and run the integration tests
# Only works on Linux.
set -euf -o pipefail
export SPIFFE_ENDPOINT_SOCKET="unix:/tmp/spire-agent/public/api.sock"
spire_version="1.11.0"
spire_folder="spire-${spire_version}"
spire_server_log_file="/tmp/spire-server/server.log"
spire_agent_log_file="/tmp/spire-agent/agent.log"
function cleanup() {
killall -9 spire-agent || true
killall -9 spire-server || true
rm -f /tmp/spire-server/private/api.sock
rm -f /tmp/spire-agent/public/api.sock
rm -rf ${spire_folder}
}
# Some cleanup: kill spire processes that could have remained from previous run
trap cleanup EXIT
# Install and run a SPIRE server
curl -s -N -L https://github.com/spiffe/spire/releases/download/v${spire_version}/spire-${spire_version}-linux-amd64-musl.tar.gz | tar xz
pushd "${spire_folder}"
mkdir -p /tmp/spire-server
bin/spire-server run -config conf/server/server.conf > "${spire_server_log_file}" 2>&1 &
spire_server_started=0
for i in {1..10}
do
if bin/spire-server healthcheck >/dev/null 2>&1; then
spire_server_started=1
break
fi
sleep 1
done
if [ ${spire_server_started} -ne 1 ]; then
cat ${spire_server_log_file} >&2
echo 'SPIRE Server failed to start' >&2
exit 1
fi
# Generate token and run Spire Agent
agent_id="spiffe://example.org/myagent"
bin/spire-server token generate -spiffeID ${agent_id} > token
cut -d ' ' -f 2 token > token_stripped
mkdir -p /tmp/spire-agent
bin/spire-agent run -config conf/agent/agent.conf -joinToken "$(< token_stripped)" > "${spire_agent_log_file}" 2>&1 &
spire_agent_started=0
for i in {1..10}
do
if bin/spire-agent healthcheck >/dev/null 2>&1; then
spire_agent_started=1
break
fi
sleep 1
done
if [ ${spire_agent_started} -ne 1 ]; then
cat ${spire_agent_log_file} >&2
echo 'SPIRE Agent failed to start' >&2
exit 1
fi
# Register the workload through UID with the SPIFFE ID "spiffe://example.org/myservice"
bin/spire-server entry create -parentID ${agent_id} -spiffeID spiffe://example.org/myservice -selector unix:uid:$(id -u)
sleep 10 # this value is derived from the default Agent sync interval
popd
# Run only the integration tests
./gradlew integrationTest

View File

@ -1,38 +0,0 @@
language: java
os:
- linux
- osx
jdk:
- openjdk15
- openjdk11
matrix:
include:
- os: linux
jdk: openjdk8
after_success:
- ./gradlew jacocoTestReport coveralls
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- "$HOME/.gradle/caches/"
- "$HOME/.gradle/wrapper/"
deploy:
- provider: releases
api_key: $GITHUB_TOKEN
file_glob: true
file: "build/libs/*"
skip_cleanup: true
on:
tags: true
condition: $GITHUB_TOKEN != ""
os: linux
jdk: openjdk15

125
CHANGELOG.md Normal file
View File

@ -0,0 +1,125 @@
# Changelog
## [0.8.12] - 2025-06-05
### Dependency updates
- Bump grpcVersion to 1.73.0 (#327)
- Bump com.nimbusds:nimbus-jose-jwt to 10.3 (#323)
- Bump io.netty:netty-transport-native-kqueue to 4.2.1.Final (#321)
- Bump com.google.protobuf:protobuf-gradle-plugin to 0.9.5 (#314)
- Bump org.projectlombok:lombok to 1.18.38 (#313)
### Documentation
- Add documentation about sslNegotiation config in java-spiffe-provider (#325)
## [0.8.11] - 2024-11-18
### Fixed
- Fix CRLF newline removal in DER format logic for Windows compatibility (#284)
### Dependency updates
- Bump io.netty:netty-transport-native-kqueue from 4.1.114.Final to 4.1.115.Final (#281)
- Bump com.nimbusds:nimbus-jose-jwt from 9.45 to 9.47 (#285)
- Bump org.projectlombok:lombok from 1.18.34 to 1.18.36 (#286)
## [0.8.10] - 2024-11-06
### Dependency updates
- Bump jupiterVersion from 5.11.2 to 5.11.3 (#273)
- Bump grpcVersion from 1.68.0 to 1.68.1 (#276)
- Bump com.nimbusds:nimbus-jose-jwt from 9.41.2 to 9.45 (#278)
## [0.8.9] - 2024-10-09
### Dependency updates
- Bump `com.google.protobuf:protoc` to `3.25.5` (#271)
## [0.8.8] - 2024-10-08
### Dependency updates
- Bump `grpcVersion` from 1.66.0 to 1.68.0 (#262)
- Bump `io.netty:netty-transport-native-kqueue` from 4.1.113.Final to 4.1.114.Final (#265)
- Bump `com.nimbusds:nimbus-jose-jwt` from 9.41.1 to 9.41.2 in (#266)
### Changed
- Updated Gradle to version 8.10.2 (#269)
## [0.8.7] - 2024-09-20
### Dependency updates
- Bump `grpcVersion` from 1.62.2 to 1.66.0 (#248)
- Bump `io.netty:netty-transport-native-kqueue` from 4.1.107.Final to 4.1.113.Final (#260)
- Bump `commons-cli:commons-cli` from 1.6.0 to 1.9.0 (#258)
- Bump `com.nimbusds:nimbus-jose-jwt` from 9.37.3 to 9.41.1 (#259)
- Bump `org.apache.commons:commons-lang3` from 3.14.0 to 3.17.0 (#255)
- Bump `org.projectlombok:lombok` from 1.18.30 to 1.18.34 (#253)
- Bump `commons-validator:commons-validator` from 1.8.0 to 1.9.0 (#251)
- Bump `jupiterVersion` from 5.10.2 to 5.11.0 (#254)
## [0.8.6] - 2024-03-04
### Dependency updates
- Bump `com.google.protobuf:protoc` from 3.25.2 to 3.25.3 (#218)
- Bump `io.grpc:grpc-protobuf`, `io.grpc:grpc-stub`, `io.grpc:grpc-netty`, `io.grpc:grpc-netty-shaded`,
and `io.grpc:protoc-gen-grpc-java` from 1.61.1 to 1.62.2 (#222)
- Bump `io.netty:netty-transport-native-kqueue` from 4.1.106.Final to 4.1.107.Final (#205)
### CI/CD Improvements
Automated build and publish process via GitHub Actions.
## [0.8.5] - 2024-14-02
### Added
- Docker container and CI workflow for `java-spiffe-helper` (#187)
### Changed
- Updated Gradle to version 8.5 (#201)
- Various enhancements in `java-spiffe-helper` (#199)
### Fixed
- Addressed a Fat Jar Assembly issue. (#198)
### Dependency updates
- Bump `io.grpc:grpc-protobuf` and `io.grpc:grpc-stub` from 1.54.0 to 1.61.1 (#202)
- Bump `commons-validator:commons-validator` from 1.7. to 1.8.0 (#197)
- Bump `commons-cli:commons-cli` from 1.5.0 to 1.6.0 (#196)
- Bump `com.google.protobuf:protoc` from 3.21.12 to 3.25.2 (#193)
- Bump `io.netty:netty-transport-native-kqueue` from 4.1.91.Final to 4.1.106.Final (#192)
- Bump `org.apache.commons:commons-lang3` from 3.12.0 to 3.14.0 (#189)
- Bump `com.nimbusds:nimbus-jose-jwt` from 9.31 to 9.37.3 (#184)
- Bump `org.projectlombok:lombok` from 1.18.26 to 1.18.30 (#170)
- Bump `com.google.protobuf:protobuf-gradle-plugin` from 0.9.2 to 0.9.4 (#153)
## [0.8.4] - 2023-04-14
### Dependencies updates
- Bump `commons-cli:commons-cli` from 1.4 to 1.5.0 (#124)
- Bump `com.google.osdetector` from 1.6.2 to 1.7.3 (#125)
- Bump `org.apache.commons:commons-lang3` from 3.11 to 3.12.0 (#129)
- Bump `org.projectlombok:lombok` from 1.18.20 to 1.18.26 (#128)
## [0.8.3] - 2023-04-13
### Added
- A `JwtSource` implementation,`CachedJwtSource`, that caches the JWT SVIDs based on their subjects and audiences (#116)
- Support for the `hint` field in the SVIDs retrieved by Workload API client (#114)

View File

@ -1 +1,13 @@
* @maxlambrecht
* @maxlambrecht @rturner3
##########################################
# Maintainers
##########################################
# Max Lambrecht
# Hewlett-Packard Enterprise
# @maxlambrecht
# Ryan Turner
# Uber Technologies, Inc.
# @rturner3

15
Dockerfile Normal file
View File

@ -0,0 +1,15 @@
FROM eclipse-temurin:17-jdk AS builder
WORKDIR /builder
COPY . /builder
RUN ./gradlew dependencies
RUN ./gradlew java-spiffe-helper:assemble -ParchiveClassifier=docker -Pversion=docker
FROM eclipse-temurin:17-jre AS runner
USER nobody
COPY conf/java-spiffe-helper.properties /app/java-spiffe-helper.properties
COPY --from=builder /builder/java-spiffe-helper/build/libs/java-spiffe-helper-docker-docker.jar /app/java-spiffe-helper.jar
ENTRYPOINT ["java", "-jar", "/app/java-spiffe-helper.jar"]
CMD ["--config", "/app/java-spiffe-helper.properties"]

View File

@ -1,7 +1,7 @@
# Java SPIFFE Library
<a href='https://travis-ci.org/spiffe/java-spiffe.svg?branch=master'><img src='https://travis-ci.org/spiffe/java-spiffe.svg?branch=master'></a>
[![Coverage Status](https://coveralls.io/repos/github/spiffe/java-spiffe/badge.svg)](https://coveralls.io/github/spiffe/java-spiffe?branch=master)
[![Build Status](https://github.com/spiffe/java-spiffe/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/spiffe/java-spiffe/actions/workflows/build.yml?query=branch%3Amain)
[![Coverage Status](https://coveralls.io/repos/github/spiffe/java-spiffe/badge.svg)](https://coveralls.io/github/spiffe/java-spiffe?branch=main)
## Overview
@ -26,7 +26,7 @@ X.509 and JWT SVIDs and bundles.
Download
--------
The JARs can be downloaded from [Maven Central](https://search.maven.org/search?q=g:io.spiffe%20AND%20v:0.6.2).
The JARs can be downloaded from [Maven Central](https://search.maven.org/search?q=g:io.spiffe%20AND%20v:0.8.12).
The dependencies can be added to `pom.xml`
@ -35,7 +35,7 @@ To import the `java-spiffe-provider` component:
<dependency>
<groupId>io.spiffe</groupId>
<artifactId>java-spiffe-provider</artifactId>
<version>0.6.2</version>
<version>0.8.12</version>
</dependency>
```
The `java-spiffe-provider` component imports the `java-spiffe-core` component.
@ -45,7 +45,7 @@ To just import the `java-spiffe-core` component:
<dependency>
<groupId>io.spiffe</groupId>
<artifactId>java-spiffe-core</artifactId>
<version>0.6.2</version>
<version>0.8.12</version>
</dependency>
```
@ -53,32 +53,70 @@ Using Gradle:
Import `java-spiffe-provider`:
```gradle
implementation group: 'io.spiffe', name: 'java-spiffe-provider', version: '0.6.2'
implementation group: 'io.spiffe', name: 'java-spiffe-provider', version: '0.8.12'
```
Import `java-spiffe-core`:
```gradle
implementation group: 'io.spiffe', name: 'java-spiffe-core', version: '0.6.2'
implementation group: 'io.spiffe', name: 'java-spiffe-core', version: '0.8.12'
```
### MacOS Support
Add to your `pom.xml`:
#### x86 Architecture
In case run on a osx-x86 architecture, add to your `pom.xml`:
```xml
<dependency>
<groupId>io.spiffe</groupId>
<artifactId>grpc-netty-macos</artifactId>
<version>0.6.2</version>
<version>0.8.12</version>
<scope>runtime</scope>
</dependency>
```
Using Gradle:
```gradle
runtimeOnly group: 'io.spiffe', name: 'grpc-netty-macos', version: '0.6.2'
runtimeOnly group: 'io.spiffe', name: 'grpc-netty-macos', version: '0.8.12'
```
### Build the JARs
#### Aarch64 (M1) Architecture
If you are running the aarch64 architecture (M1 CPUs), add to your `pom.xml`:
```xml
<dependency>
<groupId>io.spiffe</groupId>
<artifactId>grpc-netty-macos-aarch64</artifactId>
<version>0.8.12</version>
<scope>runtime</scope>
</dependency>
```
Using Gradle:
```gradle
runtimeOnly group: 'io.spiffe', name: 'grpc-netty-macos-aarch64', version: '0.8.12'
```
*Caveat: not all OpenJDK distributions are aarch64 native, make sure your JDK is also running
natively*
## Java SPIFFE Helper
The `java-spiffe-helper` module manages X.509 SVIDs and Bundles in Java Keystores.
### Docker Image
Pull the `java-spiffe-helper` image from `ghcr.io/spiffe/java-spiffe-helper:0.8.12`.
For more details, see [java-spiffe-helper/README.md](java-spiffe-helper/README.md).
## Build the JARs
On Linux or MacOS, run:
@ -93,9 +131,10 @@ All `jar` files are placed in `build/libs` folder.
For the module [java-spiffe-provider](java-spiffe-provider), a fat jar is generated with the classifier `-all-[os-classifier]`.
Fhe module [java-spiffe-helper](java-spiffe-helper), a fat jar is generated with the classifier `[os-classifier]`
For the module [java-spiffe-helper](java-spiffe-helper), a fat jar is generated with the classifier `[os-classifier]`.
Based on the OS where the build is run, the `[os-classifier]` will be:
* `-linux-x86_64` for Linux
* `-osx-x86_64` for MacOS
* `-osx-x86_64` for MacOS with x86_64 architecture
* `-osx-aarch64` for MacOS with aarch64 architecture (M1)

View File

@ -1,6 +1,7 @@
plugins {
id 'com.github.kt3k.coveralls' version '2.10.1'
id 'com.google.osdetector' version '1.6.2'
id 'com.github.kt3k.coveralls' version '2.12.2'
id 'com.google.osdetector' version '1.7.3'
id 'jvm-test-suite'
}
allprojects {
@ -12,24 +13,28 @@ allprojects {
subprojects {
group = 'io.spiffe'
version = '0.6.2'
version = project.version
ext {
grpcVersion = '1.31.1'
jupiterVersion = '5.6.2'
mockitoVersion = '3.5.2'
lombokVersion = '1.18.12'
nimbusVersion = '8.20'
grpcVersion = '1.73.0'
jupiterVersion = '5.13.2'
mockitoVersion = '4.11.0'
lombokVersion = '1.18.38'
nimbusVersion = '10.3.1'
shadowVersion = '8.1.1'
//IMPORTANT: This must be in sync with the shaded netty version in gRPC
nettyVersion = '4.2.2.Final'
}
apply plugin: 'java-library'
apply plugin: 'maven-publish'
apply plugin: 'signing'
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
withJavadocJar()
withSourcesJar()
}
@ -43,8 +48,8 @@ subprojects {
repositories {
maven {
credentials {
username = project.properties["mavenDeployUser"]
password = project.properties["mavenDeployPassword"]
username = project.properties["mavenDeployUser"] ?: System.getenv("NEXUS_USERNAME")
password = project.properties["mavenDeployPassword"] ?: System.getenv("NEXUS_TOKEN")
}
url = project.properties["mavenDeployUrl"]
}
@ -77,12 +82,15 @@ subprojects {
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'maxlambrecht'
name = 'Max Lambrecht'
email = 'maxlambrecht@gmail.com'
['maxlambrecht:Max Lambrecht', 'rturner3:Ryan Turner'].each { devData ->
developer {
def devInfo = devData.split(':')
id = devInfo[0]
name = devInfo[1]
url = 'https://github.com/' + devInfo[0]
roles = ["Maintainer"]
}
}
}
}
@ -91,12 +99,13 @@ subprojects {
}
signing {
useInMemoryPgpKeys(System.getenv('PGP_PRIVATE_KEY'), System.getenv('PGP_KEY_PASSPHRASE'))
sign publishing.publications.mavenJava
}
dependencies {
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.11'
implementation group: 'commons-validator', name: 'commons-validator', version: "1.7"
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.17.0'
implementation group: 'commons-validator', name: 'commons-validator', version: "1.9.0"
testCompileOnly group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: "${jupiterVersion}"
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: "${jupiterVersion}"
@ -105,6 +114,12 @@ subprojects {
testCompileOnly group: 'org.mockito', name: 'mockito-core', version: "${mockitoVersion}"
testRuntimeOnly group: 'org.mockito', name: 'mockito-junit-jupiter', version: "${mockitoVersion}"
if (JavaVersion.current() == JavaVersion.VERSION_1_8) {
testImplementation group: 'uk.org.webcompere', name: 'system-stubs-core', version: '2.0.3' // Last version supporting Java 8
} else {
testImplementation group: 'uk.org.webcompere', name: 'system-stubs-core', version: '2.1.8'
}
// Project Lombok dependency
compileOnly group: 'org.projectlombok', name: 'lombok', version: "${lombokVersion}"
annotationProcessor group: 'org.projectlombok', name: 'lombok', version: "${lombokVersion}"
@ -112,14 +127,10 @@ subprojects {
testAnnotationProcessor group: 'org.projectlombok', name: 'lombok', version: "${lombokVersion}"
}
test {
useJUnitPlatform()
testLogging {
afterSuite { desc, result ->
if (!desc.parent) {
println "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)"
}
testing {
suites {
test {
useJUnitJupiter()
}
}
}
@ -142,8 +153,14 @@ task jacocoTestReport(type: JacocoReport) {
}
reports {
xml.enabled true
html.enabled true
xml.required = true
html.required = true
}
}
jacocoTestReport.dependsOn {
subprojects.collectMany { project ->
project.tasks.matching { it.name in ['test'] }
}
}
@ -154,12 +171,6 @@ coveralls {
'java-spiffe-provider/src/main/java']
}
// always run the tests before generating the report
jacocoTestReport.dependsOn {
subprojects*.test
copyJars // workaround to prevent deleting the build folder before generating the reports
}
// copy submodules jars to a common folder for deploy
task copyJars(type: Copy) {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
@ -167,13 +178,4 @@ task copyJars(type: Copy) {
into "$buildDir/libs"
}
task assemble {
dependsOn subprojects.assemble
}
assemble.finalizedBy copyJars
task clean {
dependsOn subprojects.clean
delete "$buildDir"
}

View File

@ -0,0 +1,25 @@
# Example java-spiffe-helper configuration
# KeyStore Path
keyStorePath = keystore.p12
# Password for the KeyStore
keyStorePass = REPLACE_WITH_YOUR_KEYSTORE_PASSWORD
# Password for the private key within the KeyStore
keyPass = REPLACE_WITH_YOUR_PRIVATE_KEY_PASSWORD
# Path to the TrustStore file
trustStorePath = truststore.p12
# TrustStore Password: Password for the TrustStore
trustStorePass = REPLACE_WITH_YOUR_TRUSTSTORE_PASSWORD
# KeyStore Type: 'pkcs12' (default) or 'jks'
keyStoreType = pkcs12
# Key Alias: Alias of the key within the KeyStore (Default: `spiffe`)
keyAlias = spiffe
# SPIFFE Socket Path: Path to the SPIRE Agent's public API socket
spiffeSocketPath = unix:/tmp/spire-agent/public/api.sock

2
gradle.properties Normal file
View File

@ -0,0 +1,2 @@
version=0.8.12
mavenDeployUrl=https://oss.sonatype.org/service/local/staging/deploy/maven2

Binary file not shown.

View File

@ -1,6 +1,6 @@
#Mon May 21 14:01:33 ART 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip

303
gradlew vendored
View File

@ -1,78 +1,126 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright © 2015-2021 the original 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
#
# https://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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -81,7 +129,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@ -89,84 +137,109 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

56
gradlew.bat vendored
View File

@ -1,4 +1,20 @@
@if "%DEBUG%" == "" @echo off
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -9,19 +25,23 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -35,7 +55,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -45,38 +65,26 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@ -32,10 +32,10 @@ The socket endpoint address is configured through the environment variable `SPIF
configure it is by providing an `X509SourceOptions` instance to the `newSource` method:
```
X509Source.X509SourceOptions x509SourceOptions = X509Source.X509SourceOptions
DefaultX509Source.X509SourceOptions x509SourceOptions = DefaultX509Source.X509SourceOptions
.builder()
.spiffeSocketPath("unix:/tmp/agent-other.sock")
.picker(list -> list.get(list.size()-1))
.svidPicker(list -> list.get(list.size()-1))
.build();
X509Source x509Source = DefaultX509Source.newSource(x509SourceOptions);

View File

@ -4,7 +4,7 @@ buildscript {
}
dependencies {
classpath group: 'com.google.protobuf', name: 'protobuf-gradle-plugin', version: '0.8.12'
classpath group: 'com.google.protobuf', name: 'protobuf-gradle-plugin', version: '0.9.5'
}
}
@ -20,11 +20,37 @@ sourceSets {
srcDirs 'build/generated/source/proto/main/java'
}
}
integrationTest {
java {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integrationTest/java')
}
resources.srcDir file('src/integrationTest/resources')
}
}
sourcesJar.duplicatesStrategy = DuplicatesStrategy.INCLUDE
configurations {
integrationTestImplementation.extendsFrom testImplementation
integrationTestCompile.extendsFrom testCompile
integrationTestCompileOnly.extendsFrom testCompileOnly
integrationTestRuntime.extendsFrom testRuntime
integrationTestRuntimeOnly.extendsFrom testRuntimeOnly
}
task integrationTest(type: Test) {
useJUnitPlatform()
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
outputs.upToDateWhen { false }
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.12.0'
artifact = 'com.google.protobuf:protoc:3.25.5'
}
plugins {
grpc {
@ -39,16 +65,28 @@ protobuf {
}
dependencies {
if (gradle.ext.isMacOsX) {
compileOnly(project('grpc-netty-macos'))
testImplementation(project('grpc-netty-macos'))
if (osdetector.os.is('osx') ) {
project.ext.osArch = System.getProperty("os.arch")
if ("x86_64" == project.ext.osArch) {
compileOnly(project('grpc-netty-macos'))
testImplementation(project('grpc-netty-macos'))
} else if ("aarch64" == project.ext.osArch) {
compileOnly(project('grpc-netty-macos-aarch64'))
testImplementation(project('grpc-netty-macos-aarch64'))
} else {
throw new GradleException("Architecture not supported: " + project.ext.osArch)
}
} else {
compileOnly(project('grpc-netty-linux'))
testImplementation(project('grpc-netty-linux'))
}
project.ext.osArch = System.getProperty("os.arch")
implementation group: 'io.grpc', name: 'grpc-protobuf', version: "${grpcVersion}"
implementation group: 'io.grpc', name: 'grpc-stub', version: "${grpcVersion}"
testImplementation group: 'io.grpc', name: 'grpc-inprocess', version: "${grpcVersion}"
testImplementation group: 'io.grpc', name: 'grpc-testing', version: "${grpcVersion}"
compileOnly group: 'org.apache.tomcat', name: 'annotations-api', version: '6.0.53' // necessary for Java 9+
@ -57,7 +95,7 @@ dependencies {
testFixturesImplementation group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: "${nimbusVersion}"
// using bouncy castle for generating X.509 certs for testing purposes
testFixturesImplementation group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.66'
testFixturesImplementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.11'
testFixturesImplementation group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.70'
testFixturesImplementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.17.0'
}

View File

@ -0,0 +1,12 @@
description = "Java SPIFFE Library GRPC-Netty MacOS module"
dependencies {
implementation group: 'io.grpc', name: 'grpc-netty', version: "${grpcVersion}"
// version must match the one in grpc-netty
implementation group: 'io.netty', name: 'netty-transport-native-kqueue', version: "${nettyVersion}", classifier: 'osx-aarch_64'
}
jar {
archiveClassifier = ""
}

View File

@ -0,0 +1,81 @@
package io.spiffe.workloadapi.internal;
import io.grpc.ManagedChannel;
import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.kqueue.KQueueDomainSocketChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.unix.DomainSocketAddress;
import lombok.NonNull;
import lombok.val;
import org.apache.commons.lang3.SystemUtils;
import java.net.URI;
import java.util.concurrent.ExecutorService;
/**
* Factory for creating ManagedChannel instances for Mac OS.
*/
public final class GrpcManagedChannelFactory {
private static final String UNIX_SCHEME = "unix";
private static final String TCP_SCHEME = "tcp";
private GrpcManagedChannelFactory() {
}
/**
* Returns a ManagedChannelWrapper that contains a {@link ManagedChannel} to the SPIFFE Socket Endpoint provided.
*
* @param address URI representing the Workload API endpoint.
* @param executorService the executor to configure the event loop group
* @return a instance of a {@link ManagedChannelWrapper}
*/
public static ManagedChannelWrapper newChannel(@NonNull URI address, ExecutorService executorService) {
val scheme = address.getScheme();
ManagedChannelWrapper result;
switch (scheme) {
case UNIX_SCHEME:
result = createNativeSocketChannel(address, executorService);
break;
case TCP_SCHEME:
result = createTcpChannel(address);
break;
default:
throw new IllegalArgumentException("Address Scheme not supported: ");
}
return result;
}
// Create a Native Socket Channel pointing to the spiffeSocketPath
private static ManagedChannelWrapper createNativeSocketChannel(@NonNull URI address, ExecutorService executorService) {
NettyChannelBuilder channelBuilder = NettyChannelBuilder.
forAddress(new DomainSocketAddress(address.getPath()));
EventLoopGroup eventLoopGroup = configureNativeSocketChannel(channelBuilder, executorService);
ManagedChannel managedChannel = channelBuilder.usePlaintext().build();
return new ManagedChannelWrapper(managedChannel, eventLoopGroup);
}
private static ManagedChannelWrapper createTcpChannel(@NonNull URI address) {
ManagedChannel managedChannel = NettyChannelBuilder.forAddress(address.getHost(), address.getPort())
.negotiationType(NegotiationType.PLAINTEXT)
.build();
return new ManagedChannelWrapper(managedChannel);
}
private static EventLoopGroup configureNativeSocketChannel(@NonNull NettyChannelBuilder channelBuilder, ExecutorService executorService) {
if (SystemUtils.IS_OS_MAC) {
// nThreads = 0 -> use Netty default
KQueueEventLoopGroup eventLoopGroup = new KQueueEventLoopGroup(0, executorService);
channelBuilder.eventLoopGroup(eventLoopGroup)
// avoid warning Unknown channel option 'SO_KEEPALIVE'
.withOption(ChannelOption.SO_KEEPALIVE, null)
.channelType(KQueueDomainSocketChannel.class);
return eventLoopGroup;
}
throw new IllegalStateException("Operating System is not supported.");
}
}

View File

@ -0,0 +1,51 @@
package io.spiffe.workloadapi.internal;
import io.grpc.ManagedChannel;
import io.netty.channel.EventLoopGroup;
import java.io.Closeable;
/**
* Wraps a {@link ManagedChannel} along with the {@link EventLoopGroup} in order to
* have more control and be able to shutdown the channel properly
* calling the shutdownGracefully method on the EventLoopGroup to prevent
* that some threads remain active.
*/
public class ManagedChannelWrapper implements Closeable {
private final ManagedChannel managedChannel;
private final EventLoopGroup eventLoopGroup;
/**
* Constructor.
*
* @param managedChannel an instance of {@link ManagedChannel}
* @param eventLoopGroup an instance of {@link EventLoopGroup}
*/
public ManagedChannelWrapper(ManagedChannel managedChannel, EventLoopGroup eventLoopGroup) {
this.managedChannel = managedChannel;
this.eventLoopGroup = eventLoopGroup;
}
/**
* Constructor.
*
* @param managedChannel a {@link ManagedChannel}
*/
public ManagedChannelWrapper(ManagedChannel managedChannel) {
this.managedChannel = managedChannel;
this.eventLoopGroup = null;
}
@Override
public void close() {
if (eventLoopGroup != null) {
eventLoopGroup.shutdownGracefully();
}
managedChannel.shutdown();
}
public ManagedChannel getChannel() {
return managedChannel;
}
}

View File

@ -2,7 +2,7 @@ description = "Java SPIFFE Library GRPC-Netty MacOS module"
dependencies {
implementation group: 'io.grpc', name: 'grpc-netty', version: "${grpcVersion}"
implementation group: 'io.netty', name: 'netty-transport-native-kqueue', version: '4.1.51.Final', classifier: 'osx-x86_64'
implementation group: 'io.netty', name: 'netty-transport-native-kqueue', version: "${nettyVersion}", classifier: 'osx-x86_64'
}
jar {

View File

@ -0,0 +1,79 @@
package io.spiffe.workloadapi;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.BundleNotFoundException;
import io.spiffe.exception.JwtBundleException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.exception.SocketEndpointAddressException;
import io.spiffe.exception.X509BundleException;
import io.spiffe.exception.X509ContextException;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.spiffeid.TrustDomain;
import io.spiffe.svid.jwtsvid.JwtSvid;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
// To run these tests there should be a Workload API running, the SPIFFE_ENDPOINT_SOCKET env variable should be defined,
// and there should be a registration entry with a SPIFFE-ID = 'spiffe://example.org/myservice' and a selector unix:uid with
// the user id used to run the process.
class WorkloadApiIntegrationTest {
private WorkloadApiClient client;
@BeforeEach
void setup() throws SocketEndpointAddressException {
client = DefaultWorkloadApiClient.newClient();
}
@Test
void testFetchX509Context() throws X509ContextException, BundleNotFoundException {
X509Context response = client.fetchX509Context();
Assertions.assertEquals(response.getDefaultSvid().getSpiffeId(), SpiffeId.parse("spiffe://example.org/myservice"));
Assertions.assertNotNull(response.getX509BundleSet().getBundleForTrustDomain(TrustDomain.parse("example.org")));
}
@Test
void testFetchJwtBundles() throws BundleNotFoundException, JwtBundleException {
JwtBundleSet response = client.fetchJwtBundles();
Assertions.assertNotNull(response.getBundleForTrustDomain(TrustDomain.parse("example.org")));
}
@Test
void testFetchX509Bundles() throws BundleNotFoundException, X509BundleException {
X509BundleSet response = client.fetchX509Bundles();
Assertions.assertNotNull(response.getBundleForTrustDomain(TrustDomain.parse("example.org")));
}
@Test
void testFetchJwtSvid() throws JwtSvidException {
JwtSvid response = client.fetchJwtSvid("audience1", "audience2");
Assertions.assertEquals(response.getSpiffeId(), SpiffeId.parse("spiffe://example.org/myservice"));
Assertions.assertTrue(response.getAudience().contains("audience1"));
Assertions.assertTrue(response.getAudience().contains("audience2"));
Assertions.assertNotNull(response.getToken());
}
@Test
void testValidateJwtSvid() throws JwtSvidException {
String token = client.fetchJwtSvid("audience1", "audience2").getToken();
JwtSvid response = client.validateJwtSvid(token, "audience1");
Assertions.assertEquals(response.getSpiffeId(), SpiffeId.parse("spiffe://example.org/myservice"));
Assertions.assertTrue(response.getAudience().contains("audience1"));
Assertions.assertTrue(response.getAudience().contains("audience2"));
}
@Test
void testValidateJwtSvid_invalid_audience() throws JwtSvidException {
String token = client.fetchJwtSvid("audience1", "audience2").getToken();
try {
client.validateJwtSvid(token, "other");
Assertions.fail();
} catch (JwtSvidException e) {
Assertions.assertEquals("Error validating JWT SVID", e.getMessage());
}
}
}

View File

@ -5,7 +5,7 @@ import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import io.spiffe.Algorithm;
import io.spiffe.internal.JwtSignatureAlgorithm;
import io.spiffe.bundle.BundleSource;
import io.spiffe.exception.AuthorityNotFoundException;
import io.spiffe.exception.BundleNotFoundException;
@ -72,7 +72,7 @@ public class JwtBundle implements BundleSource<JwtBundle> {
try {
val jwkSet = JWKSet.load(bundlePath.toFile());
return toJwtBundle(trustDomain, jwkSet);
} catch (IOException | ParseException | JOSEException e) {
} catch (IllegalArgumentException | IOException | ParseException | JOSEException e) {
val error = "Could not load bundle from file: %s";
throw new JwtBundleException(String.format(error, bundlePath.toString()), e);
}
@ -189,7 +189,7 @@ public class JwtBundle implements BundleSource<JwtBundle> {
}
private static PublicKey getPublicKey(final JWK jwk) throws JOSEException, ParseException, KeyException {
val family = Algorithm.Family.parse(jwk.getKeyType().getValue());
val family = JwtSignatureAlgorithm.Family.parse(jwk.getKeyType().getValue());
final PublicKey publicKey;
switch (family) {

View File

@ -0,0 +1,11 @@
package io.spiffe.exception;
/**
* Runtime exception thrown when there is a validation error on
* a SpiffeId.
*/
public class InvalidSpiffeIdException extends RuntimeException {
public InvalidSpiffeIdException(String s) {
super(s);
}
}

View File

@ -10,10 +10,6 @@ import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
@ -44,10 +40,6 @@ import static org.apache.commons.lang3.StringUtils.startsWith;
*/
public class CertificateUtils {
// Algorithms for verifying private and public keys
private static final String SHA_512_WITH_ECDSA = "SHA512withECDSA";
private static final String SHA_512_WITH_RSA = "SHA512withRSA";
private static final String SPIFFE_PREFIX = "spiffe://";
private static final int SAN_VALUE_INDEX = 1;
private static final String PUBLIC_KEY_INFRASTRUCTURE_ALGORITHM = "PKIX";
@ -154,24 +146,6 @@ public class CertificateUtils {
return spiffeId.getTrustDomain();
}
/**
* Validates that the private key and the public key in the x509Certificate match by
* creating a signature with the private key and verifying with the public key.
*
* @throws InvalidKeyException if the keys don't match
*/
public static void validatePrivateKey(final PrivateKey privateKey, final X509Certificate x509Certificate) throws InvalidKeyException {
AsymmetricKeyAlgorithm algorithm = AsymmetricKeyAlgorithm.parse(privateKey.getAlgorithm());
switch (algorithm) {
case RSA:
verifyKeys(privateKey, x509Certificate.getPublicKey(), SHA_512_WITH_RSA);
break;
case EC:
verifyKeys(privateKey, x509Certificate.getPublicKey(), SHA_512_WITH_ECDSA);
}
}
public static boolean isCA(final X509Certificate cert) {
return cert.getBasicConstraints() != -1;
}
@ -202,23 +176,6 @@ public class CertificateUtils {
return keySpec;
}
private static void verifyKeys(final PrivateKey privateKey, final PublicKey publicKey, final String algorithm) throws InvalidKeyException {
final byte[] challenge = new SecureRandom().generateSeed(100);
try {
Signature sig = Signature.getInstance(algorithm);
sig.initSign(privateKey);
sig.update(challenge);
byte[] signature = sig.sign();
sig.initVerify(publicKey);
sig.update(challenge);
if (!sig.verify(signature)) {
throw new InvalidKeyException("Private Key does not match Certificate Public Key");
}
} catch (NoSuchAlgorithmException | SignatureException e) {
throw new InvalidKeyException("Private and Public Keys could not be verified", e);
}
}
private static List<String> getSpiffeIds(final X509Certificate certificate) throws CertificateParsingException {
if (certificate.getSubjectAlternativeNames() == null) {
return Collections.emptyList();
@ -272,8 +229,8 @@ public class CertificateUtils {
// Given a private key in PEM format, encode it as DER
private static byte[] toDerFormat(final byte[] privateKeyPem) throws InvalidKeyException {
String privateKeyAsString = new String(privateKeyPem);
privateKeyAsString = privateKeyAsString.replaceAll("(-+BEGIN PRIVATE KEY-+\\r?\\n|-+END PRIVATE KEY-+\\r?\\n?)", "");
privateKeyAsString = privateKeyAsString.replaceAll("\n", "");
privateKeyAsString = privateKeyAsString.replaceAll("(-+BEGIN PRIVATE KEY-+|-+END PRIVATE KEY-+)", "");
privateKeyAsString = privateKeyAsString.replaceAll("\r?\n", "");
val decoder = Base64.getDecoder();
try {
return decoder.decode(privateKeyAsString);

View File

@ -1,13 +1,15 @@
package io.spiffe;
package io.spiffe.internal;
import lombok.NonNull;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Represents JWT Algorithms.
* Represents JWT Signature Supported Algorithms.
*/
public enum Algorithm {
public enum JwtSignatureAlgorithm {
/**
* ECDSA algorithm using SHA-256 hash algorithm.
@ -52,16 +54,11 @@ public enum Algorithm {
/**
* RSASSA-PSS using SHA-512 and MGF1 padding with SHA-512.
*/
PS512("PS512"),
/**
* Non-Supported algorithm.
*/
OTHER("OTHER");
PS512("PS512");
private final String name;
Algorithm(final String name) {
JwtSignatureAlgorithm(final String name) {
this.name = name;
}
@ -74,13 +71,12 @@ public enum Algorithm {
*/
public enum Family {
RSA("RSA", RS256, RS384, RS512, PS256, PS384, PS512),
EC("EC", ES256, ES384, ES512),
OTHER("UNKNOWN");
EC("EC", ES256, ES384, ES512);
private final String name;
private final Set<Algorithm> algorithms;
private final Set<JwtSignatureAlgorithm> algorithms;
Family(final String name, final Algorithm... algs) {
Family(final String name, final JwtSignatureAlgorithm... algs) {
this.name = name;
algorithms = new HashSet<>();
Collections.addAll(algorithms, algs);
@ -90,7 +86,7 @@ public enum Algorithm {
return name;
}
public boolean contains(final Algorithm a) {
public boolean contains(final JwtSignatureAlgorithm a) {
return algorithms.contains(a);
}
@ -101,14 +97,14 @@ public enum Algorithm {
} else if (s.equals(EC.getName())) {
family = EC;
} else {
family = OTHER;
throw new IllegalArgumentException("Unsupported JWT family algorithm: " + s);
}
return family;
}
}
public static Algorithm parse(final String s) {
final Algorithm algorithm;
public static JwtSignatureAlgorithm parse(@NonNull final String s) {
final JwtSignatureAlgorithm algorithm;
if (s.equals(RS256.getName())) {
algorithm = RS256;
} else if (s.equals(RS384.getName())) {
@ -128,7 +124,7 @@ public enum Algorithm {
} else if (s.equals(PS512.getName())) {
algorithm = PS512;
} else {
algorithm = OTHER;
throw new IllegalArgumentException("Unsupported JWT algorithm: " + s);
}
return algorithm;
}

View File

@ -1,23 +1,32 @@
package io.spiffe.spiffeid;
import io.spiffe.exception.InvalidSpiffeIdException;
import lombok.NonNull;
import lombok.Value;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import java.net.URI;
import java.util.Arrays;
import java.util.stream.Collectors;
import static io.spiffe.spiffeid.TrustDomain.isValidTrustDomainChar;
/**
* Represents a SPIFFE ID as defined in SPIFFE standard.
* Represents a SPIFFE ID as defined in the SPIFFE standard.
* <p>
* @see <a href="https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md">https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md</a>
*/
@Value
public class SpiffeId {
public static final String SPIFFE_SCHEME = "spiffe";
static final String SPIFFE_SCHEME = "spiffe";
static final String SCHEME_PREFIX = SPIFFE_SCHEME + "://";
static final String EMPTY = "Cannot be empty";
static final String MISSING_TRUST_DOMAIN = "Trust domain is missing";
static final String WRONG_SCHEME = "Scheme is missing or invalid";
static final String BAD_TRUST_DOMAIN_CHAR = "Trust domain characters are limited to lowercase letters, numbers, dots, dashes, and underscores";
static final String BAD_PATH_SEGMENT_CHAR = "Path segment characters are limited to letters, numbers, dots, dashes, and underscores";
static final String DOT_SEGMENT = "Path cannot contain dot segments";
static final String EMPTY_SEGMENT = "Path cannot contain empty segments";
static final String TRAILING_SLASH = "Path cannot have a trailing slash";
TrustDomain trustDomain;
@ -28,42 +37,71 @@ public class SpiffeId {
this.path = path;
}
/**
* Returns an instance representing a SPIFFE ID, containing the trust domain and
* a path generated joining the segments (e.g. /path1/path2).
* Returns a new SPIFFE ID in the given trust domain with joined
* path segments. The path segments must be valid according to the SPIFFE
* specification and must not contain path separators.
* See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path
*
* @param trustDomain an instance of a {@link TrustDomain}
* @param segments a list of string path segments
*
* @return a {@link SpiffeId}
* @throws InvalidSpiffeIdException if a given path segment contains an invalid char or empty or dot segment
*/
public static SpiffeId of(@NonNull final TrustDomain trustDomain, final String... segments) {
val path = Arrays.stream(segments)
.filter(StringUtils::isNotBlank)
.map(SpiffeId::normalize)
.map(s -> '/' + s)
.collect(Collectors.joining());
return new SpiffeId(trustDomain, path);
public static SpiffeId fromSegments(@NonNull final TrustDomain trustDomain, final String... segments) {
StringBuilder path = new StringBuilder();
for (String p : segments) {
validatePath(p);
path.append('/');
path.append(p);
}
return new SpiffeId(trustDomain, path.toString());
}
/**
* Parses a SPIFFE ID from a string (e.g. spiffe://example.org/test).
*
* @param spiffeIdAsString a String representing a SPIFFE ID
* @param id a String representing a SPIFFE ID
* @return A {@link SpiffeId}
* @throws IllegalArgumentException if the given string cannot be parsed
* @throws IllegalArgumentException if the given string is empty
* @throws InvalidSpiffeIdException if the given string id contain an invalid scheme, invalid char or empty or dot segment
*/
public static SpiffeId parse(final String spiffeIdAsString) {
if (StringUtils.isBlank(spiffeIdAsString)) {
throw new IllegalArgumentException("SPIFFE ID cannot be empty");
public static SpiffeId parse(final String id) {
if (StringUtils.isBlank(id)) {
throw new IllegalArgumentException(EMPTY);
}
val uri = URI.create(normalize(spiffeIdAsString));
validateUri(uri);
if (!id.contains(SCHEME_PREFIX)) {
throw new InvalidSpiffeIdException(WRONG_SCHEME);
}
val trustDomain = TrustDomain.of(uri.getHost());
val path = uri.getPath();
return new SpiffeId(trustDomain, path);
String rest = id.substring(SCHEME_PREFIX.length());
int i = 0;
for (char c : rest.toCharArray()) {
if (c == '/'){
break;
}
if (!isValidTrustDomainChar(c)) {
throw new InvalidSpiffeIdException(BAD_TRUST_DOMAIN_CHAR);
}
i++;
}
if (i == 0) {
throw new InvalidSpiffeIdException(MISSING_TRUST_DOMAIN);
}
String td = rest.substring(0, i);
String path = rest.substring(i);
if (StringUtils.isNotBlank(path)) {
validatePath(path);
}
return new SpiffeId(new TrustDomain(td), path);
}
/**
@ -86,34 +124,52 @@ public class SpiffeId {
return String.format("%s://%s%s", SPIFFE_SCHEME, this.trustDomain.toString(), this.path);
}
private static String normalize(final String s) {
return s.toLowerCase().trim();
/**
* Validates that a path string is a conformant path for a SPIFFE ID.
* See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path
*/
public static void validatePath(String path) {
if (StringUtils.isBlank(path)) {
throw new IllegalArgumentException(EMPTY);
}
int segmentStart = 0;
int segmentEnd = 0;
for ( ; segmentEnd < path.length(); segmentEnd++) {
char c = path.charAt(segmentEnd);
if (c == '/') {
switch (path.substring(segmentStart, segmentEnd)) {
case "/":
throw new InvalidSpiffeIdException(EMPTY_SEGMENT);
case "/.":
case "/..":
throw new InvalidSpiffeIdException(DOT_SEGMENT);
}
segmentStart = segmentEnd;
continue;
}
if (!isValidPathSegmentChar(c)) {
throw new InvalidSpiffeIdException(BAD_PATH_SEGMENT_CHAR);
}
}
switch (path.substring(segmentStart, segmentEnd)) {
case "/":
throw new InvalidSpiffeIdException(TRAILING_SLASH);
case "/.":
case "/..":
throw new InvalidSpiffeIdException(DOT_SEGMENT);
}
}
private static void validateUri(final URI uri) {
val scheme = uri.getScheme();
if (!SpiffeId.SPIFFE_SCHEME.equals(scheme)) {
throw new IllegalArgumentException("SPIFFE ID: invalid scheme");
}
if (uri.getUserInfo() != null) {
throw new IllegalArgumentException("SPIFFE ID: user info is not allowed");
}
if (StringUtils.isBlank(uri.getHost())) {
throw new IllegalArgumentException("SPIFFE ID: trust domain is empty");
}
if (uri.getPort() != -1) {
throw new IllegalArgumentException("SPIFFE ID: port is not allowed");
}
if (StringUtils.isNotBlank(uri.getFragment())) {
throw new IllegalArgumentException("SPIFFE ID: fragment is not allowed");
}
if (StringUtils.isNotBlank(uri.getRawQuery())) {
throw new IllegalArgumentException("SPIFFE ID: query is not allowed");
}
private static boolean isValidPathSegmentChar(char c) {
if (c >= 'a' && c <= 'z')
return true;
if (c >= 'A' && c <= 'Z')
return true;
if (c >= '0' && c <= '9')
return true;
return c == '-' || c == '.' || c == '_';
}
}

View File

@ -21,7 +21,7 @@ public final class SpiffeIdUtils {
private static final char DEFAULT_CHAR_SEPARATOR = '|';
private static final Set<Character> SUPPORTED_SEPARATORS = Sets.newHashSet(DEFAULT_CHAR_SEPARATOR, ' ');
private static final Set<Character> SUPPORTED_SEPARATORS = Sets.newHashSet(DEFAULT_CHAR_SEPARATOR, ' ', ',');
private SpiffeIdUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");

View File

@ -1,50 +1,50 @@
package io.spiffe.spiffeid;
import io.spiffe.exception.InvalidSpiffeIdException;
import lombok.NonNull;
import lombok.Value;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import java.net.URI;
import java.net.URISyntaxException;
import static io.spiffe.spiffeid.SpiffeId.BAD_TRUST_DOMAIN_CHAR;
/**
* Represents a normalized SPIFFE trust domain (e.g. 'domain.test').
* Represents the name of a SPIFFE trust domain (e.g. 'domain.test').
*/
@Value
public class TrustDomain {
String name;
private TrustDomain(final String trustDomain) {
TrustDomain(final String trustDomain) {
this.name = trustDomain;
}
/**
* Creates a trust domain.
*
* @param trustDomain a trust domain represented as a string, must not be blank.
* @param idOrName the name of a Trust Domain or a string representing a SpiffeId.
*
* @return an instance of a {@link TrustDomain}
* @throws IllegalArgumentException if the given string is blank or cannot be parsed
* @throws IllegalArgumentException if the given string is empty.
* @throws InvalidSpiffeIdException if the given string contains an invalid char.
*/
public static TrustDomain of(@NonNull final String trustDomain) {
if (StringUtils.isBlank(trustDomain)) {
throw new IllegalArgumentException("Trust domain cannot be empty");
public static TrustDomain parse(@NonNull final String idOrName) {
if (StringUtils.isBlank(idOrName)) {
throw new IllegalArgumentException("Trust domain is missing");
}
URI uri;
try {
val normalized = normalize(trustDomain);
uri = new URI(normalized);
validateUri(uri);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e.getMessage(), e);
// Something looks kinda like a scheme separator, let's try to parse as
// an ID. We use :/ instead of :// since the diagnostics are better for
// a bad input like spiffe:/trustdomain.
if (idOrName.contains(":/")) {
SpiffeId spiffeId = SpiffeId.parse(idOrName);
return spiffeId.getTrustDomain();
}
val host = uri.getHost();
validateHost(host);
return new TrustDomain(host);
validateTrustDomainName(idOrName);
return new TrustDomain(idOrName);
}
/**
@ -52,9 +52,10 @@ public class TrustDomain {
*
* @param segments path segments
* @return a {@link SpiffeId} with the current trust domain and the given path segments
* @throws InvalidSpiffeIdException if the given path segments contain invalid chars or empty or dot segments
*/
public SpiffeId newSpiffeId(final String... segments) {
return SpiffeId.of(this, segments);
return SpiffeId.fromSegments(this, segments);
}
/**
@ -67,29 +68,32 @@ public class TrustDomain {
return name;
}
private static void validateHost(final String host) {
if (StringUtils.isBlank(host)) {
throw new IllegalArgumentException("Trust domain cannot be empty");
/**
* Returns the trust domain as SPIFFE ID string (e.g. 'spiffe://example.org')
*
* @return a String formatted as a SPIFFE ID
*/
public String toIdString() {
return SpiffeId.SPIFFE_SCHEME + "://" + name;
}
static void validateTrustDomainName(final String name) {
for (char c : name.toCharArray()) {
if (!isValidTrustDomainChar(c)) {
throw new InvalidSpiffeIdException(BAD_TRUST_DOMAIN_CHAR);
}
}
}
private static void validateUri(final URI uri) {
val scheme = uri.getScheme();
if (!SpiffeId.SPIFFE_SCHEME.equals(scheme)) {
throw new IllegalArgumentException("Invalid scheme");
static boolean isValidTrustDomainChar(char c) {
if (c >= 'a' && c <= 'z') {
return true;
}
val port = uri.getPort();
if (port != -1) {
throw new IllegalArgumentException("Trust Domain: port is not allowed");
if (c >= '0' && c <= '9') {
return true;
}
}
private static String normalize(final String s) {
String result = s.toLowerCase().trim();
if (!result.contains("://")) {
result = SpiffeId.SPIFFE_SCHEME.concat("://").concat(result);
}
return result;
return c == '-' || c == '.' || c == '_';
}
}

View File

@ -1,18 +1,21 @@
package io.spiffe.svid.jwtsvid;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.ECDSAVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import io.spiffe.Algorithm;
import io.spiffe.bundle.BundleSource;
import io.spiffe.bundle.jwtbundle.JwtBundle;
import io.spiffe.exception.AuthorityNotFoundException;
import io.spiffe.exception.BundleNotFoundException;
import io.spiffe.exception.InvalidSpiffeIdException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.internal.JwtSignatureAlgorithm;
import io.spiffe.spiffeid.SpiffeId;
import lombok.NonNull;
import lombok.Value;
@ -61,16 +64,35 @@ public class JwtSvid {
*/
String token;
/**
* Issued at time of JWT-SVID as present in 'iat' claim.
*/
Date issuedAt;
/**
* Hint is an operator-specified string used to provide guidance on how this
* identity should be used by a workload when more than one SVID is returned.
*/
String hint;
public static final String HEADER_TYP_JWT = "JWT";
public static final String HEADER_TYP_JOSE = "JOSE";
private JwtSvid(final SpiffeId spiffeId,
final Set<String> audience,
final Date expiry,
final Map<String, Object> claims,
final String token) {
final Set<String> audience,
final Date issuedAt,
final Date expiry,
final Map<String, Object> claims,
final String token,
final String hint
) {
this.spiffeId = spiffeId;
this.audience = audience;
this.expiry = expiry;
this.claims = claims;
this.token = token;
this.issuedAt = issuedAt;
this.hint = hint;
}
/**
@ -84,32 +106,73 @@ public class JwtSvid {
* @param audience audience as a list of strings used to validate the 'aud' claim
* @return an instance of a {@link JwtSvid} with a SPIFFE ID parsed from the 'sub', audience from 'aud', and expiry
* from 'exp' claim.
* @throws JwtSvidException when the token expired or the expiration claim is missing,
* when the algorithm is not supported, when the header 'kid' is missing,
* when the signature cannot be verified, or
* when the 'aud' claim has an audience that is not in the audience list
* provided as parameter
* @throws IllegalArgumentException when the token is blank or cannot be parsed
* @throws BundleNotFoundException if the bundle for the trust domain of the spiffe id from the 'sub'
* cannot be found
* in the JwtBundleSource
* @throws AuthorityNotFoundException if the authority cannot be found in the bundle using the value from
* the 'kid' header
* @throws JwtSvidException when the token expired or the expiration claim is missing,
* when the algorithm is not supported (See {@link JwtSignatureAlgorithm}),
* when the header 'kid' is missing,
* when the header 'typ' is present and is not 'JWT' or 'JOSE'
* when the signature cannot be verified,
* when the 'aud' claim has an audience that is not in the audience list
* provided as parameter
* @throws IllegalArgumentException when the token is blank or cannot be parsed
* @throws BundleNotFoundException if the bundle for the trust domain of the spiffe id from the 'sub'
* cannot be found in the JwtBundleSource
* @throws AuthorityNotFoundException if the authority cannot be found in the bundle using the value from
* the 'kid' header
*/
public static JwtSvid parseAndValidate(@NonNull final String token,
@NonNull final BundleSource<JwtBundle> jwtBundleSource,
@NonNull final Set<String> audience)
throws JwtSvidException, BundleNotFoundException, AuthorityNotFoundException {
return parseAndValidate(token, jwtBundleSource, audience, null);
}
/**
* Parses and validates a JWT-SVID token and returns an instance of {@link JwtSvid}.
* <p>
* The JWT-SVID signature is verified using the JWT bundle source.
*
* @param token a token as a string that is parsed and validated
* @param jwtBundleSource an implementation of a {@link BundleSource} that provides the JWT authorities to
* verify the signature
* @param audience audience as a list of strings used to validate the 'aud' claim
* @param hint a hint that can be used to provide guidance on how this identity should be used
* @return an instance of a {@link JwtSvid} with a SPIFFE ID parsed from the 'sub', audience from 'aud', and expiry
* from 'exp' claim.
* @throws JwtSvidException when the token expired or the expiration claim is missing,
* when the algorithm is not supported (See {@link JwtSignatureAlgorithm}),
* when the header 'kid' is missing,
* when the header 'typ' is present and is not 'JWT' or 'JOSE'
* when the signature cannot be verified,
* when the 'aud' claim has an audience that is not in the audience list
* provided as parameter
* @throws IllegalArgumentException when the token is blank or cannot be parsed
* @throws BundleNotFoundException if the bundle for the trust domain of the spiffe id from the 'sub'
* cannot be found in the JwtBundleSource
* @throws AuthorityNotFoundException if the authority cannot be found in the bundle using the value from
* the 'kid' header
*/
public static JwtSvid parseAndValidate(@NonNull final String token,
@NonNull final BundleSource<JwtBundle> jwtBundleSource,
@NonNull final Set<String> audience,
final String hint
)
throws JwtSvidException, BundleNotFoundException, AuthorityNotFoundException {
if (StringUtils.isBlank(token)) {
throw new IllegalArgumentException("Token cannot be blank");
}
val signedJwt = getSignedJWT(token);
val claimsSet = getJwtClaimsSet(signedJwt);
validateTypeHeader(signedJwt.getHeader());
JwtSignatureAlgorithm algorithm = parseAlgorithm(signedJwt.getHeader().getAlgorithm());
val claimsSet = getJwtClaimsSet(signedJwt);
validateAudience(claimsSet.getAudience(), audience);
val issuedAt = claimsSet.getIssueTime();
val expirationTime = claimsSet.getExpirationTime();
validateExpiration(expirationTime);
@ -119,11 +182,11 @@ public class JwtSvid {
val keyId = getKeyId(signedJwt.getHeader());
val jwtAuthority = jwtBundle.findJwtAuthority(keyId);
val algorithm = signedJwt.getHeader().getAlgorithm().getName();
verifySignature(signedJwt, jwtAuthority, algorithm, keyId);
val claimAudience = new HashSet<>(claimsSet.getAudience());
return new JwtSvid(spiffeId, claimAudience, expirationTime, claimsSet.getClaims(), token);
return new JwtSvid(spiffeId, claimAudience, issuedAt, expirationTime, claimsSet.getClaims(), token, hint);
}
/**
@ -135,27 +198,55 @@ public class JwtSvid {
* @param audience audience as a list of strings used to validate the 'aud' claim
* @return an instance of a {@link JwtSvid} with a SPIFFE ID parsed from the 'sub', audience from 'aud', and expiry
* from 'exp' claim.
* @throws JwtSvidException when the token expired or the expiration claim is missing, or when
* the 'aud' has an audience that is not in the audience provided as parameter
* @throws IllegalArgumentException when the token cannot be parsed
* @throws JwtSvidException when the token expired or the expiration claim is missing,
* when the 'aud' has an audience that is not in the audience provided as parameter,
* when the 'alg' is not supported (See {@link JwtSignatureAlgorithm}),
* when the header 'typ' is present and is not 'JWT' or 'JOSE'.
* @throws IllegalArgumentException when the token cannot be parsed
*/
public static JwtSvid parseInsecure(@NonNull final String token, @NonNull final Set<String> audience) throws JwtSvidException {
return parseInsecure(token, audience, null);
}
/**
* Parses and validates a JWT-SVID token and returns an instance of a {@link JwtSvid}.
* <p>
* The JWT-SVID signature is not verified.
*
* @param token a token as a string that is parsed and validated
* @param audience audience as a list of strings used to validate the 'aud'
* @param hint a hint that can be used to provide guidance on how this identity should be used
* @return an instance of a {@link JwtSvid} with a SPIFFE ID parsed from the 'sub', audience from 'aud', and expiry
* from 'exp' claim.
* @throws JwtSvidException when the token expired or the expiration claim is missing,
* when the 'aud' has an audience that is not in the audience provided as parameter,
* when the 'alg' is not supported (See {@link JwtSignatureAlgorithm}),
* when the header 'typ' is present and is not 'JWT' or 'JOSE'.
* @throws IllegalArgumentException when the token cannot be parsed
*/
public static JwtSvid parseInsecure(@NonNull final String token, @NonNull final Set<String> audience, final String hint) throws JwtSvidException {
if (StringUtils.isBlank(token)) {
throw new IllegalArgumentException("Token cannot be blank");
}
val signedJwt = getSignedJWT(token);
val claimsSet = getJwtClaimsSet(signedJwt);
validateTypeHeader(signedJwt.getHeader());
parseAlgorithm(signedJwt.getHeader().getAlgorithm());
val claimsSet = getJwtClaimsSet(signedJwt);
validateAudience(claimsSet.getAudience(), audience);
val issuedAt = claimsSet.getIssueTime();
val expirationTime = claimsSet.getExpirationTime();
validateExpiration(expirationTime);
val spiffeId = getSpiffeIdOfSubject(claimsSet);
val claimAudience = new HashSet<>(claimsSet.getAudience());
return new JwtSvid(spiffeId, claimAudience, expirationTime, claimsSet.getClaims(), token);
return new JwtSvid(spiffeId, claimAudience, issuedAt, expirationTime, claimsSet.getClaims(), token, hint);
}
/**
@ -178,6 +269,16 @@ public class JwtSvid {
return new Date(expiry.getTime());
}
/**
* Returns the SVID hint.
*
* @return the SVID hint
*/
public String getHint() {
return hint;
}
/**
* Returns the map of claims.
*
@ -216,7 +317,7 @@ public class JwtSvid {
return signedJwt;
}
private static void verifySignature(final SignedJWT signedJwt, final PublicKey jwtAuthority, final String algorithm, final String keyId) throws JwtSvidException {
private static void verifySignature(final SignedJWT signedJwt, final PublicKey jwtAuthority, final JwtSignatureAlgorithm algorithm, final String keyId) throws JwtSvidException {
boolean verify;
try {
val verifier = getJwsVerifier(jwtAuthority, algorithm);
@ -230,12 +331,11 @@ public class JwtSvid {
}
}
private static JWSVerifier getJwsVerifier(final PublicKey jwtAuthority, final String algorithm) throws JOSEException, JwtSvidException {
private static JWSVerifier getJwsVerifier(final PublicKey jwtAuthority, final JwtSignatureAlgorithm algorithm) throws JOSEException, JwtSvidException {
JWSVerifier verifier;
val alg = Algorithm.parse(algorithm);
if (Algorithm.Family.EC.contains(alg)) {
if (JwtSignatureAlgorithm.Family.EC.contains(algorithm)) {
verifier = new ECDSAVerifier((ECPublicKey) jwtAuthority);
} else if (Algorithm.Family.RSA.contains(alg)) {
} else if (JwtSignatureAlgorithm.Family.RSA.contains(algorithm)) {
verifier = new RSASSAVerifier((RSAPublicKey) jwtAuthority);
} else {
throw new JwtSvidException(String.format("Unsupported token signature algorithm %s", algorithm));
@ -272,7 +372,7 @@ public class JwtSvid {
try {
return SpiffeId.parse(subject);
} catch (IllegalArgumentException e) {
} catch (InvalidSpiffeIdException e) {
throw new JwtSvidException(String.format("Subject %s cannot be parsed as a SPIFFE ID", subject), e);
}
@ -284,4 +384,28 @@ public class JwtSvid {
throw new JwtSvidException(String.format("expected audience in %s (audience=%s)", expectedAudiences, audClaim));
}
}
private static JwtSignatureAlgorithm parseAlgorithm(JWSAlgorithm algorithm) throws JwtSvidException {
if (algorithm == null) {
throw new JwtSvidException("JWT header 'alg' is required");
}
try {
return JwtSignatureAlgorithm.parse(algorithm.getName());
} catch (IllegalArgumentException e) {
throw new JwtSvidException(e.getMessage(), e);
}
}
private static void validateTypeHeader(JWSHeader headers) throws JwtSvidException {
final JOSEObjectType type = headers.getType();
// if it's not present -> OK
if (type == null || StringUtils.isBlank(type.toString())) {
return;
}
final String typValue = type.toString();
if (!HEADER_TYP_JWT.equals(typValue) && !HEADER_TYP_JOSE.equals(typValue)) {
throw new JwtSvidException(String.format("If JWT header 'typ' is present, it must be either 'JWT' or 'JOSE'. Got: '%s'.", type.toString()));
}
}
}

View File

@ -2,6 +2,9 @@ package io.spiffe.svid.jwtsvid;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.spiffeid.SpiffeId;
import lombok.NonNull;
import java.util.List;
/**
* Represents a source of SPIFFE JWT-SVIDs.
@ -28,4 +31,25 @@ public interface JwtSvidSource {
* @throws JwtSvidException when there is an error fetching the JWT SVID
*/
JwtSvid fetchJwtSvid(SpiffeId subject, String audience, String... extraAudiences) throws JwtSvidException;
/**
* Fetches all SPIFFE JWT-SVIDs on one-shot blocking call.
*
* @param audience the audience of the JWT-SVID
* @param extraAudience the extra audience for the JWT_SVID
* @return all of {@link JwtSvid} object
* @throws JwtSvidException if there is an error fetching or processing the JWT from the Workload API
*/
List<JwtSvid> fetchJwtSvids(@NonNull String audience, String... extraAudience) throws JwtSvidException;
/**
* Fetches all SPIFFE JWT-SVIDs on one-shot blocking call.
*
* @param subject a SPIFFE ID
* @param audience the audience of the JWT-SVID
* @param extraAudience the extra audience for the JWT_SVID
* @return all of {@link JwtSvid} object
* @throws JwtSvidException if there is an error fetching or processing the JWT from the Workload API
*/
List<JwtSvid> fetchJwtSvids(@NonNull SpiffeId subject, @NonNull String audience, String... extraAudience) throws JwtSvidException;
}

View File

@ -41,13 +41,23 @@ public class X509Svid {
PrivateKey privateKey;
/**
* Hint is an operator-specified string used to provide guidance on how this
* identity should be used by a workload when more than one SVID is returned.
*/
String hint;
private X509Svid(
final SpiffeId spiffeId,
final List<X509Certificate> chain,
final PrivateKey privateKey) {
final PrivateKey privateKey,
final String hint
) {
this.spiffeId = spiffeId;
this.chain = chain;
this.privateKey = privateKey;
this.hint = hint;
}
/**
@ -59,6 +69,16 @@ public class X509Svid {
return chain.get(0);
}
/**
* Returns the SVID hint.
*
* @return the SVID hint
*/
public String getHint() {
return hint;
}
/**
* Returns the chain of X.509 certificates.
*
@ -93,7 +113,7 @@ public class X509Svid {
} catch (IOException e) {
throw new X509SvidException("Cannot read private key file", e);
}
return createX509Svid(certsBytes, privateKeyBytes, KeyFileFormat.PEM);
return createX509Svid(certsBytes, privateKeyBytes, KeyFileFormat.PEM, null);
}
/**
@ -109,7 +129,24 @@ public class X509Svid {
*/
public static X509Svid parse(@NonNull final byte[] certsBytes, @NonNull final byte[] privateKeyBytes)
throws X509SvidException {
return createX509Svid(certsBytes, privateKeyBytes, KeyFileFormat.PEM);
return parse(certsBytes, privateKeyBytes, null);
}
/**
* Parses the X.509 SVID from PEM or DER blocks containing certificate chain and key
* bytes. The key must be a PEM block with PKCS#8.
* <p>
* It is assumed that the leaf certificate is always the first certificate in the parsed chain.
*
* @param certsBytes chain of certificates as a byte array
* @param privateKeyBytes private key as byte array
* @param hint a hint that can be used to provide guidance on how this identity should be used
* @return a {@link X509Svid} parsed from the given certBytes and privateKeyBytes
* @throws X509SvidException if the given certsBytes or privateKeyBytes cannot be parsed
*/
public static X509Svid parse(@NonNull final byte[] certsBytes, @NonNull final byte[] privateKeyBytes, final String hint)
throws X509SvidException {
return createX509Svid(certsBytes, privateKeyBytes, KeyFileFormat.PEM, hint);
}
/**
@ -125,7 +162,25 @@ public class X509Svid {
*/
public static X509Svid parseRaw(@NonNull final byte[] certsBytes,
@NonNull final byte[] privateKeyBytes) throws X509SvidException {
return createX509Svid(certsBytes, privateKeyBytes, KeyFileFormat.DER);
return parseRaw(certsBytes, privateKeyBytes, null);
}
/**
* Parses the X509-SVID from certificate and key bytes. The certificate must be ASN.1 DER (concatenated with
* no intermediate padding if there are more than one certificate). The key must be a PKCS#8 ASN.1 DER.
* <p>
* It is assumed that the leaf certificate is always the first certificate in the parsed chain.
*
* @param certsBytes chain of certificates as a byte array
* @param privateKeyBytes private key as byte array
* @param hint a hint that can be used to provide guidance on how this identity should be used
* @return a {@link X509Svid} parsed from the given certBytes and privateKeyBytes
* @throws X509SvidException if the given certsBytes or privateKeyBytes cannot be parsed
*/
public static X509Svid parseRaw(@NonNull final byte[] certsBytes,
@NonNull final byte[] privateKeyBytes,
final String hint) throws X509SvidException {
return createX509Svid(certsBytes, privateKeyBytes, KeyFileFormat.DER, hint);
}
/**
@ -139,13 +194,13 @@ public class X509Svid {
private static X509Svid createX509Svid(final byte[] certsBytes,
final byte[] privateKeyBytes,
final KeyFileFormat keyFileFormat) throws X509SvidException {
final KeyFileFormat keyFileFormat,
final String hint) throws X509SvidException {
val x509Certificates = generateX509Certificates(certsBytes);
val privateKey = generatePrivateKey(privateKeyBytes, keyFileFormat, x509Certificates);
val spiffeId = getSpiffeId(x509Certificates);
validatePrivateKey(privateKey, x509Certificates);
validateLeafCertificate(x509Certificates.get(0));
// there are intermediate CA certificates
@ -153,7 +208,7 @@ public class X509Svid {
validateSigningCertificates(x509Certificates);
}
return new X509Svid(spiffeId, x509Certificates, privateKey);
return new X509Svid(spiffeId, x509Certificates, privateKey, hint);
}
private static SpiffeId getSpiffeId(final List<X509Certificate> x509Certificates) throws X509SvidException {
@ -225,13 +280,4 @@ public class X509Svid {
throw new X509SvidException("Leaf certificate must not have 'cRLSign' as key usage");
}
}
private static void validatePrivateKey(final PrivateKey privateKey, final List<X509Certificate> x509Certificates)
throws X509SvidException {
try {
CertificateUtils.validatePrivateKey(privateKey, x509Certificates.get(0));
} catch (InvalidKeyException e) {
throw new X509SvidException("Private Key does not match Certificate Public Key", e);
}
}
}

View File

@ -0,0 +1,338 @@
package io.spiffe.workloadapi;
import io.spiffe.bundle.jwtbundle.JwtBundle;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509Bundle;
import io.spiffe.exception.*;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.spiffeid.TrustDomain;
import io.spiffe.svid.jwtsvid.JwtSvid;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.java.Log;
import lombok.val;
import org.apache.commons.lang3.tuple.ImmutablePair;
import java.io.Closeable;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import static io.spiffe.workloadapi.internal.ThreadUtils.await;
/**
* Represents a source of SPIFFE JWT SVIDs and JWT bundles maintained via the Workload API.
* The JWT SVIDs are cached and fetchJwtSvid methods return from cache
* checking that the JWT SVID has still at least half of its lifetime.
*/
@Log
public class CachedJwtSource implements JwtSource {
static final String TIMEOUT_SYSTEM_PROPERTY = "spiffe.newJwtSource.timeout";
static final Duration DEFAULT_TIMEOUT =
Duration.parse(System.getProperty(TIMEOUT_SYSTEM_PROPERTY, "PT0S"));
// Synchronized map of JWT SVIDs, keyed by a pair of SPIFFE ID and a Set of audiences strings.
// This map is used to cache the JWT SVIDs and avoid fetching them from the Workload API.
private final
Map<ImmutablePair<SpiffeId, Set<String>>, List<JwtSvid>> jwtSvids = new ConcurrentHashMap<>();
private JwtBundleSet bundles;
private final WorkloadApiClient workloadApiClient;
private volatile boolean closed;
private Clock clock;
// private constructor
private CachedJwtSource(final WorkloadApiClient workloadApiClient) {
this.clock = Clock.systemDefaultZone();
this.workloadApiClient = workloadApiClient;
}
/**
* Creates a new Cached JWT source. It blocks until the initial update with the JWT bundles
* has been received from the Workload API or until the timeout configured
* through the system property `spiffe.newJwtSource.timeout` expires.
* If no timeout is configured, it blocks until it gets a JWT update from the Workload API.
* <p>
* It uses the default address socket endpoint from the environment variable to get the Workload API address.
*
* @return an instance of {@link DefaultJwtSource}, with the JWT bundles initialized
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
* @throws JwtSourceException if the source could not be initialized
*/
public static JwtSource newSource() throws JwtSourceException, SocketEndpointAddressException {
JwtSourceOptions options = JwtSourceOptions.builder().initTimeout(DEFAULT_TIMEOUT).build();
return newSource(options);
}
/**
* Creates a new JWT source. It blocks until the initial update with the JWT bundles
* has been received from the Workload API, doing retries with an exponential backoff policy,
* or until the initTimeout has expired.
* <p>
* If the timeout is not provided in the options, the default timeout is read from the
* system property `spiffe.newJwtSource.timeout`. If none is configured, this method will
* block until the JWT bundles can be retrieved from the Workload API.
* <p>
* The {@link WorkloadApiClient} can be provided in the options, if it is not,
* a new client is created.
*
* @param options {@link JwtSourceOptions}
* @return an instance of {@link CachedJwtSource}, with the JWT bundles initialized
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
* @throws JwtSourceException if the source could not be initialized
*/
public static JwtSource newSource(@NonNull final JwtSourceOptions options)
throws SocketEndpointAddressException, JwtSourceException {
if (options.getWorkloadApiClient() == null) {
options.setWorkloadApiClient(createClient(options));
}
if (options.getInitTimeout() == null) {
options.setInitTimeout(DEFAULT_TIMEOUT);
}
CachedJwtSource jwtSource = new CachedJwtSource(options.getWorkloadApiClient());
try {
jwtSource.init(options.getInitTimeout());
} catch (Exception e) {
jwtSource.close();
throw new JwtSourceException("Error creating JWT source", e);
}
return jwtSource;
}
/**
* Fetches a JWT SVID for the given audiences. The JWT SVID is cached and
* returned from the cache if it still has at least half of its lifetime.
*
* @param audience the audience
* @param extraAudiences a list of extra audiences as an array of String
* @return a {@link JwtSvid}
* @throws JwtSvidException
*/
@Override
public JwtSvid fetchJwtSvid(final String audience, final String... extraAudiences) throws JwtSvidException {
if (isClosed()) {
throw new IllegalStateException("JWT SVID source is closed");
}
return getJwtSvids(audience, extraAudiences).get(0);
}
/**
* Fetches a JWT SVID for the given subject and audience. The JWT SVID is cached and
* returned from cache if it has still at least half of its lifetime.
*
* @return a {@link JwtSvid}
* @throws IllegalStateException if the source is closed
*/
@Override
public JwtSvid fetchJwtSvid(final SpiffeId subject, final String audience, final String... extraAudiences)
throws JwtSvidException {
if (isClosed()) {
throw new IllegalStateException("JWT SVID source is closed");
}
return getJwtSvids(subject, audience, extraAudiences).get(0);
}
/**
* Fetches a list of JWT SVIDs for the given audience. The JWT SVIDs are cached and
* returned from cache if they have still at least half of their lifetime.
*
* @return a list of {@link JwtSvid}s
* @throws IllegalStateException if the source is closed
*/
@Override
public List<JwtSvid> fetchJwtSvids(final String audience, final String... extraAudiences) throws JwtSvidException {
if (isClosed()) {
throw new IllegalStateException("JWT SVID source is closed");
}
return getJwtSvids(audience, extraAudiences);
}
/**
* Fetches a list of JWT SVIDs for the given subject and audience. The JWT SVIDs are cached and
* returned from cache if they have still at least half of their lifetime.
*
* @return a list of {@link JwtSvid}s
* @throws IllegalStateException if the source is closed
*/
@Override
public List<JwtSvid> fetchJwtSvids(final SpiffeId subject, final String audience, final String... extraAudiences)
throws JwtSvidException {
if (isClosed()) {
throw new IllegalStateException("JWT SVID source is closed");
}
return getJwtSvids(subject, audience, extraAudiences);
}
/**
* Returns the JWT bundle for a given trust domain.
*
* @return an instance of a {@link X509Bundle}
* @throws BundleNotFoundException is there is no bundle for the trust domain provided
* @throws IllegalStateException if the source is closed
*/
@Override
public JwtBundle getBundleForTrustDomain(@NonNull final TrustDomain trustDomain) throws BundleNotFoundException {
if (isClosed()) {
throw new IllegalStateException("JWT bundle source is closed");
}
return bundles.getBundleForTrustDomain(trustDomain);
}
/**
* Closes this source, dropping the connection to the Workload API.
* Other source methods will return an error after close has been called.
* <p>
* It is marked with {@link SneakyThrows} because it is not expected to throw
* the checked exception defined on the {@link Closeable} interface.
*/
@SneakyThrows
@Override
public void close() {
if (!closed) {
synchronized (this) {
if (!closed) {
workloadApiClient.close();
closed = true;
}
}
}
}
// Check if the jwtSvids map contains the cacheKey, returns it if it does and the JWT SVID has not passed its half lifetime.
// If the cache does not contain the key or the JWT SVID has passed its half lifetime, make a new FetchJWTSVID call to the Workload API,
// adds the JWT SVIDs to the cache map and returns them.
// Only one thread can fetch new JWT SVIDs and update the cache at a time.
private List<JwtSvid> getJwtSvids(SpiffeId subject, String audience, String... extraAudiences) throws JwtSvidException {
Set<String> audiencesSet = getAudienceSet(audience, extraAudiences);
ImmutablePair<SpiffeId, Set<String>> cacheKey = new ImmutablePair<>(subject, audiencesSet);
List<JwtSvid> svidList = jwtSvids.get(cacheKey);
if (svidList != null && !isTokenPastHalfLifetime(svidList.get(0))) {
return svidList;
}
// even using ConcurrentHashMap, there is a possibility of multiple threads trying to fetch new JWT SVIDs at the same time.
synchronized (this) {
// Check again if the jwtSvids map contains the cacheKey, and return the entry if it exists and the JWT SVID has not passed its half lifetime.
// If it does not exist or the JWT-SVID has passed half its lifetime, call the Workload API to fetch new JWT-SVIDs,
// add them to the cache map, and return the list of JWT-SVIDs.
svidList = jwtSvids.get(cacheKey);
if (svidList != null && !isTokenPastHalfLifetime(svidList.get(0))) {
return svidList;
}
if (cacheKey.left == null) {
svidList = workloadApiClient.fetchJwtSvids(audience, extraAudiences);
} else {
svidList = workloadApiClient.fetchJwtSvids(cacheKey.left, audience, extraAudiences);
}
jwtSvids.put(cacheKey, svidList);
return svidList;
}
}
private List<JwtSvid> getJwtSvids(String audience, String... extraAudiences) throws JwtSvidException {
return getJwtSvids(null, audience, extraAudiences);
}
private static Set<String> getAudienceSet(String audience, String[] extraAudiences) {
Set<String> audiencesString;
if (extraAudiences != null && extraAudiences.length > 0) {
audiencesString = new HashSet<>(Arrays.asList(extraAudiences));
audiencesString.add(audience);
} else {
audiencesString = Collections.singleton(audience);
}
return audiencesString;
}
private boolean isTokenPastHalfLifetime(JwtSvid jwtSvid) {
Instant now = clock.instant();
val halfLife = new Date(jwtSvid.getExpiry().getTime() - (jwtSvid.getExpiry().getTime() - jwtSvid.getIssuedAt().getTime()) / 2);
val halfLifeInstant = Instant.ofEpochMilli(halfLife.getTime());
return now.isAfter(halfLifeInstant);
}
private void init(final Duration timeout) throws TimeoutException {
CountDownLatch done = new CountDownLatch(1);
setJwtBundlesWatcher(done);
boolean success;
if (timeout.isZero()) {
await(done);
success = true;
} else {
success = await(done, timeout.getSeconds(), TimeUnit.SECONDS);
}
if (!success) {
throw new TimeoutException("Timeout waiting for JWT bundles update");
}
}
private void setJwtBundlesWatcher(final CountDownLatch done) {
workloadApiClient.watchJwtBundles(new Watcher<JwtBundleSet>() {
@Override
public void onUpdate(final JwtBundleSet update) {
log.log(Level.INFO, "Received JwtBundleSet update");
setJwtBundleSet(update);
done.countDown();
}
@Override
public void onError(final Throwable error) {
log.log(Level.SEVERE, "Error in JwtBundleSet watcher", error);
done.countDown();
throw new WatcherException("Error fetching JwtBundleSet", error);
}
});
}
private void setJwtBundleSet(final JwtBundleSet update) {
synchronized (this) {
this.bundles = update;
}
}
private boolean isClosed() {
synchronized (this) {
return closed;
}
}
private static WorkloadApiClient createClient(final JwtSourceOptions options)
throws SocketEndpointAddressException {
val clientOptions = DefaultWorkloadApiClient.ClientOptions
.builder()
.spiffeSocketPath(options.getSpiffeSocketPath())
.build();
return DefaultWorkloadApiClient.newClient(clientOptions);
}
void setClock(Clock clock) {
this.clock = clock;
}
}

View File

@ -3,25 +3,18 @@ package io.spiffe.workloadapi;
import io.spiffe.bundle.jwtbundle.JwtBundle;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509Bundle;
import io.spiffe.exception.BundleNotFoundException;
import io.spiffe.exception.JwtSourceException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.exception.SocketEndpointAddressException;
import io.spiffe.exception.WatcherException;
import io.spiffe.exception.*;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.spiffeid.TrustDomain;
import io.spiffe.svid.jwtsvid.JwtSvid;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.java.Log;
import lombok.val;
import java.io.Closeable;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ -86,18 +79,18 @@ public class DefaultJwtSource implements JwtSource {
*/
public static JwtSource newSource(@NonNull final JwtSourceOptions options)
throws SocketEndpointAddressException, JwtSourceException {
if (options.workloadApiClient == null) {
options.workloadApiClient = createClient(options);
if (options.getWorkloadApiClient()== null) {
options.setWorkloadApiClient(createClient(options));
}
if (options.initTimeout == null) {
options.initTimeout = DEFAULT_TIMEOUT;
if (options.getInitTimeout()== null) {
options.setInitTimeout(DEFAULT_TIMEOUT);
}
DefaultJwtSource jwtSource = new DefaultJwtSource(options.workloadApiClient);
DefaultJwtSource jwtSource = new DefaultJwtSource(options.getWorkloadApiClient());
try {
jwtSource.init(options.initTimeout);
jwtSource.init(options.getInitTimeout());
} catch (Exception e) {
jwtSource.close();
throw new JwtSourceException("Error creating JWT source", e);
@ -130,6 +123,30 @@ public class DefaultJwtSource implements JwtSource {
return workloadApiClient.fetchJwtSvid(subject, audience, extraAudiences);
}
@Override
public List<JwtSvid> fetchJwtSvids(String audience, String... extraAudiences) throws JwtSvidException {
if (isClosed()) {
throw new IllegalStateException("JWT SVID source is closed");
}
return workloadApiClient.fetchJwtSvids(audience, extraAudiences);
}
/**
* Fetches all new JWT SVIDs from the Workload API for the given subject SPIFFE ID and audiences.
*
* @return all {@link JwtSvid}s
* @throws IllegalStateException if the source is closed
*/
@Override
public List<JwtSvid> fetchJwtSvids(final SpiffeId subject, final String audience, final String... extraAudiences)
throws JwtSvidException {
if (isClosed()) {
throw new IllegalStateException("JWT SVID source is closed");
}
return workloadApiClient.fetchJwtSvids(subject, audience, extraAudiences);
}
/**
* Returns the JWT bundle for a given trust domain.
*
@ -216,42 +233,8 @@ public class DefaultJwtSource implements JwtSource {
throws SocketEndpointAddressException {
val clientOptions = DefaultWorkloadApiClient.ClientOptions
.builder()
.spiffeSocketPath(options.spiffeSocketPath)
.spiffeSocketPath(options.getSpiffeSocketPath())
.build();
return DefaultWorkloadApiClient.newClient(clientOptions);
}
/**
* Options to configure a {@link DefaultJwtSource}.
* <p>
* <code>spiffeSocketPath</code> Address to the Workload API, if it is not set, the default address will be used.
* <p>
* <code>initTimeout</code> Timeout for initializing the instance. If it is not defined, the timeout is read
* from the System property `spiffe.newJwtSource.timeout'. If this is also not defined, no default timeout is applied.
* <p>
* <code>workloadApiClient</code> A custom instance of a {@link WorkloadApiClient}, if it is not set,
* a new client will be created.
*/
@Data
public static class JwtSourceOptions {
@Setter(AccessLevel.NONE)
private String spiffeSocketPath;
@Setter(AccessLevel.NONE)
private Duration initTimeout;
@Setter(AccessLevel.NONE)
private WorkloadApiClient workloadApiClient;
@Builder
public JwtSourceOptions(
final String spiffeSocketPath,
final WorkloadApiClient workloadApiClient,
final Duration initTimeout) {
this.spiffeSocketPath = spiffeSocketPath;
this.workloadApiClient = workloadApiClient;
this.initTimeout = initTimeout;
}
}
}

View File

@ -2,9 +2,11 @@ package io.spiffe.workloadapi;
import io.grpc.Context;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.JwtBundleException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.exception.SocketEndpointAddressException;
import io.spiffe.exception.X509BundleException;
import io.spiffe.exception.X509ContextException;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.svid.jwtsvid.JwtSvid;
@ -37,7 +39,9 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Level;
import static io.spiffe.workloadapi.StreamObservers.getJwtBundleStreamObserver;
import static io.spiffe.workloadapi.StreamObservers.getX509BundlesStreamObserver;
import static io.spiffe.workloadapi.StreamObservers.getX509ContextStreamObserver;
import static org.apache.commons.lang3.StringUtils.EMPTY;
/**
* Represents a client to interact with the Workload API.
@ -180,6 +184,33 @@ public final class DefaultWorkloadApiClient implements WorkloadApiClient {
this.cancellableContexts.add(cancellableContext);
}
/**
* {@inheritDoc}
*/
@Override
public X509BundleSet fetchX509Bundles() throws X509BundleException {
try (val cancellableContext = Context.current().withCancellation()) {
return cancellableContext.call(this::callFetchX509Bundles);
} catch (Exception e) {
throw new X509BundleException("Error fetching X.509 bundles", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void watchX509Bundles(@NonNull final Watcher<X509BundleSet> watcher) {
val retryHandler = new RetryHandler(exponentialBackoffPolicy, retryExecutor);
val cancellableContext = Context.current().withCancellation();
val streamObserver =
getX509BundlesStreamObserver(watcher, retryHandler, cancellableContext, workloadApiAsyncStub);
cancellableContext.run(() -> workloadApiAsyncStub.fetchX509Bundles(newX509BundlesRequest(), streamObserver));
this.cancellableContexts.add(cancellableContext);
}
/**
* {@inheritDoc}
*/
@ -211,6 +242,40 @@ public final class DefaultWorkloadApiClient implements WorkloadApiClient {
}
}
/**
* {@inheritDoc}
*/
@Override
public List<JwtSvid> fetchJwtSvids(@NonNull String audience, String... extraAudience) throws JwtSvidException {
final Set<String> audParam = createAudienceSet(audience, extraAudience);
try (val cancellableContext = Context.current().withCancellation()) {
return cancellableContext.call(() -> callFetchJwtSvids(audParam));
} catch (Exception e) {
throw new JwtSvidException("Error fetching JWT SVID", e);
}
}
/**
* {@inheritDoc}
*
* @return
*/
@Override
public List<JwtSvid> fetchJwtSvids(@NonNull final SpiffeId subject,
@NonNull final String audience,
final String... extraAudience)
throws JwtSvidException {
final Set<String> audParam = createAudienceSet(audience, extraAudience);
try (val cancellableContext = Context.current().withCancellation()) {
return cancellableContext.call(() -> callFetchJwtSvids(subject, audParam));
} catch (Exception e) {
throw new JwtSvidException("Error fetching JWT SVID", e);
}
}
/**
* {@inheritDoc}
*/
@ -242,7 +307,7 @@ public final class DefaultWorkloadApiClient implements WorkloadApiClient {
if (response == null || StringUtils.isBlank(response.getSpiffeId())) {
throw new JwtSvidException("Error validating JWT SVID. Empty response from Workload API");
}
return JwtSvid.parseInsecure(token, Collections.singleton(audience));
return JwtSvid.parseInsecure(token, Collections.singleton(audience), EMPTY);
}
/**
@ -275,6 +340,7 @@ public final class DefaultWorkloadApiClient implements WorkloadApiClient {
if (managedChannel != null) {
managedChannel.close();
}
retryExecutor.shutdown();
executorService.shutdown();
closed = true;
@ -289,13 +355,18 @@ public final class DefaultWorkloadApiClient implements WorkloadApiClient {
return GrpcConversionUtils.toX509Context(x509SvidResponse);
}
private X509BundleSet callFetchX509Bundles() throws X509BundleException {
val x509BundlesResponse = workloadApiBlockingStub.fetchX509Bundles(newX509BundlesRequest());
return GrpcConversionUtils.toX509BundleSet(x509BundlesResponse);
}
private JwtSvid callFetchJwtSvid(final SpiffeId subject, final Set<String> audience) throws JwtSvidException {
val jwtSvidRequest = Workload.JWTSVIDRequest.newBuilder()
.setSpiffeId(subject.toString())
.addAllAudience(audience)
.build();
val response = workloadApiBlockingStub.fetchJWTSVID(jwtSvidRequest);
return processJwtSvidResponse(response, audience);
return processJwtSvidResponse(response, audience, true).get(0);
}
private JwtSvid callFetchJwtSvid(final Set<String> audience) throws JwtSvidException {
@ -303,20 +374,53 @@ public final class DefaultWorkloadApiClient implements WorkloadApiClient {
.addAllAudience(audience)
.build();
val response = workloadApiBlockingStub.fetchJWTSVID(jwtSvidRequest);
return processJwtSvidResponse(response, audience);
return processJwtSvidResponse(response, audience, true).get(0);
}
private JwtSvid processJwtSvidResponse(Workload.JWTSVIDResponse response, Set<String> audience) throws JwtSvidException {
if (response.getSvidsList() == null || response.getSvidsList().size() == 0) {
private List<JwtSvid> callFetchJwtSvids(final SpiffeId subject, final Set<String> audience) throws JwtSvidException {
val jwtSvidRequest = Workload.JWTSVIDRequest.newBuilder()
.setSpiffeId(subject.toString())
.addAllAudience(audience)
.build();
val response = workloadApiBlockingStub.fetchJWTSVID(jwtSvidRequest);
return processJwtSvidResponse(response, audience, false);
}
private List<JwtSvid> callFetchJwtSvids(final Set<String> audience) throws JwtSvidException {
val jwtSvidRequest = Workload.JWTSVIDRequest.newBuilder()
.addAllAudience(audience)
.build();
val response = workloadApiBlockingStub.fetchJWTSVID(jwtSvidRequest);
return processJwtSvidResponse(response, audience, false);
}
private List<JwtSvid> processJwtSvidResponse(Workload.JWTSVIDResponse response, Set<String> audience, boolean firstOnly) throws JwtSvidException {
if (response.getSvidsList() == null || response.getSvidsList().isEmpty()) {
throw new JwtSvidException("JWT SVID response from the Workload API is empty");
}
return JwtSvid.parseInsecure(response.getSvids(0).getSvid(), audience);
int n = response.getSvidsCount();
if (firstOnly) {
n = 1;
}
ArrayList<JwtSvid> svids = new ArrayList<>(n);
HashSet<String> hints = new HashSet<>();
for (int i = 0; i < n; i++) {
// In the event of more than one JWTSVID message with the same hint value set, then the first message in the
// list SHOULD be selected.
if (hints.contains(response.getSvids(i).getHint())) {
continue;
}
val svid = JwtSvid.parseInsecure(response.getSvids(i).getSvid(), audience, response.getSvids(i).getHint());
hints.add(svid.getHint());
svids.add(svid);
}
return svids;
}
private JwtBundleSet callFetchBundles() throws JwtBundleException {
val request = Workload.JWTBundlesRequest.newBuilder().build();
val bundlesResponse = workloadApiBlockingStub.fetchJWTBundles(request);
return GrpcConversionUtils.toBundleSet(bundlesResponse);
return GrpcConversionUtils.toJwtBundleSet(bundlesResponse);
}
private Set<String> createAudienceSet(final String audience, final String[] extraAudience) {
@ -330,6 +434,10 @@ public final class DefaultWorkloadApiClient implements WorkloadApiClient {
return Workload.X509SVIDRequest.newBuilder().build();
}
private Workload.X509BundlesRequest newX509BundlesRequest() {
return Workload.X509BundlesRequest.newBuilder().build();
}
private Workload.JWTBundlesRequest newJwtBundlesRequest() {
return Workload.JWTBundlesRequest.newBuilder().build();
}

View File

@ -20,6 +20,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
/**
* Utility methods for converting GRPC objects to JAVA-SPIFFE domain objects.
@ -39,7 +40,7 @@ final class GrpcConversionUtils {
}
static X509Context toX509Context(final Workload.X509SVIDResponse x509SvidResponse) throws X509ContextException {
if (x509SvidResponse.getSvidsList() == null || x509SvidResponse.getSvidsList().size() == 0) {
if (x509SvidResponse.getSvidsList() == null || x509SvidResponse.getSvidsList().isEmpty()) {
throw new X509ContextException("X.509 Context response from the Workload API is empty");
}
@ -49,16 +50,39 @@ final class GrpcConversionUtils {
return X509Context.of(x509SvidList, bundleSet);
}
static JwtBundleSet toBundleSet(final Iterator<Workload.JWTBundlesResponse> bundlesResponseIterator) throws JwtBundleException {
public static X509BundleSet toX509BundleSet(Iterator<Workload.X509BundlesResponse> bundlesResponseIterator) throws X509BundleException {
if (!bundlesResponseIterator.hasNext()) {
throw new X509BundleException("X.509 Bundle response from the Workload API is empty");
}
val bundlesResponse = bundlesResponseIterator.next();
return toX509BundleSet(bundlesResponse);
}
static X509BundleSet toX509BundleSet(final Workload.X509BundlesResponse bundlesResponse) throws X509BundleException {
val bundlesCount = bundlesResponse.getBundlesCount();
if (bundlesCount == 0) {
throw new X509BundleException("X.509 Bundle response from the Workload API is empty");
}
final List<X509Bundle> x509Bundles = new ArrayList<>(bundlesCount);
for (Map.Entry<String, ByteString> entry : bundlesResponse.getBundlesMap().entrySet()) {
X509Bundle x509Bundle = createX509Bundle(entry);
x509Bundles.add(x509Bundle);
}
return X509BundleSet.of(x509Bundles);
}
static JwtBundleSet toJwtBundleSet(final Iterator<Workload.JWTBundlesResponse> bundlesResponseIterator) throws JwtBundleException {
if (!bundlesResponseIterator.hasNext()) {
throw new JwtBundleException("JWT Bundle response from the Workload API is empty");
}
val bundlesResponse = bundlesResponseIterator.next();
return toBundleSet(bundlesResponse);
return toJwtBundleSet(bundlesResponse);
}
static JwtBundleSet toBundleSet(final Workload.JWTBundlesResponse bundlesResponse) throws JwtBundleException {
static JwtBundleSet toJwtBundleSet(final Workload.JWTBundlesResponse bundlesResponse) throws JwtBundleException {
if (bundlesResponse.getBundlesMap().size() == 0) {
throw new JwtBundleException("JWT Bundle response from the Workload API is empty");
}
@ -90,7 +114,7 @@ final class GrpcConversionUtils {
// Process federated bundles
Set<Map.Entry<String, ByteString>> federatedBundles = x509SvidResponse.getFederatedBundlesMap().entrySet();
for (Map.Entry<String, ByteString> bundleEntry : federatedBundles) {
TrustDomain trustDomain = TrustDomain.of(bundleEntry.getKey());
TrustDomain trustDomain = TrustDomain.parse(bundleEntry.getKey());
byte[] bundleBytes = bundleEntry.getValue().toByteArray();
val bundle = parseX509Bundle(trustDomain, bundleBytes);
x509BundleList.add(bundle);
@ -109,9 +133,16 @@ final class GrpcConversionUtils {
private static List<X509Svid> getListOfX509Svid(final Workload.X509SVIDResponse x509SvidResponse) throws X509ContextException{
final List<X509Svid> result = new ArrayList<>();
HashSet<String> hints = new HashSet<>();
for (Workload.X509SVID x509SVID : x509SvidResponse.getSvidsList()) {
// In the event of more than one X509SVID message with the same hint value set, then the first message in the
// list SHOULD be selected.
if (hints.contains(x509SVID.getHint())) {
continue;
}
val svid = createAndValidateX509Svid(x509SVID);
hints.add(svid.getHint());
result.add(svid);
}
return result;
@ -121,9 +152,9 @@ final class GrpcConversionUtils {
byte[] certsBytes = x509SVID.getX509Svid().toByteArray();
byte[] privateKeyBytes = x509SVID.getX509SvidKey().toByteArray();
X509Svid svid = null;
X509Svid svid;
try {
svid = X509Svid.parseRaw(certsBytes, privateKeyBytes);
svid = X509Svid.parseRaw(certsBytes, privateKeyBytes, x509SVID.getHint());
} catch (X509SvidException e) {
throw new X509ContextException("X.509 SVID response could not be processed", e);
}
@ -142,8 +173,14 @@ final class GrpcConversionUtils {
}
private static JwtBundle createJwtBundle(Map.Entry<String, ByteString> entry) throws JwtBundleException {
TrustDomain trustDomain = TrustDomain.of(entry.getKey());
TrustDomain trustDomain = TrustDomain.parse(entry.getKey());
byte[] bundleBytes = entry.getValue().toByteArray();
return JwtBundle.parse(trustDomain, bundleBytes);
}
private static X509Bundle createX509Bundle(Map.Entry<String, ByteString> bundleEntry) throws X509BundleException {
TrustDomain trustDomain = TrustDomain.parse(bundleEntry.getKey());
byte[] bundleBytes = bundleEntry.getValue().toByteArray();
return X509Bundle.parse(trustDomain, bundleBytes);
}
}

View File

@ -0,0 +1,43 @@
package io.spiffe.workloadapi;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.Setter;
import java.time.Duration;
/**
* Options to configure a {@link JwtSource}.
* <p>
* <code>spiffeSocketPath</code> Address to the Workload API, if it is not set, the default address will be used.
* <p>
* <code>initTimeout</code> Timeout for initializing the instance. If it is not defined, the timeout is read
* from the System property `spiffe.newJwtSource.timeout'. If this is also not defined, no default timeout is applied.
* <p>
* <code>workloadApiClient</code> A custom instance of a {@link WorkloadApiClient}, if it is not set,
* a new client will be created.
*/
@Data
public class JwtSourceOptions {
@Setter(AccessLevel.PUBLIC)
private String spiffeSocketPath;
@Setter(AccessLevel.PUBLIC)
private Duration initTimeout;
@Setter(AccessLevel.PUBLIC)
private WorkloadApiClient workloadApiClient;
@Builder
public JwtSourceOptions(
final String spiffeSocketPath,
final WorkloadApiClient workloadApiClient,
final Duration initTimeout) {
this.spiffeSocketPath = spiffeSocketPath;
this.workloadApiClient = workloadApiClient;
this.initTimeout = initTimeout;
}
}

View File

@ -4,7 +4,9 @@ import io.grpc.Context;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.JwtBundleException;
import io.spiffe.exception.X509BundleException;
import io.spiffe.exception.X509ContextException;
import io.spiffe.workloadapi.grpc.SpiffeWorkloadAPIGrpc;
import io.spiffe.workloadapi.grpc.Workload;
@ -18,6 +20,7 @@ import java.util.logging.Level;
final class StreamObservers {
private static final String INVALID_ARGUMENT = "INVALID_ARGUMENT";
private static final String STREAM_IS_COMPLETED = "Workload API stream is completed";
private StreamObservers() {
}
@ -42,13 +45,15 @@ final class StreamObservers {
@Override
public void onError(final Throwable t) {
log.log(Level.SEVERE, "X.509 context observer error", t);
if (Status.fromThrowable(t).getCode() != Status.Code.CANCELLED) {
log.log(Level.SEVERE, "X.509 context observer error", t);
}
handleWatchX509ContextError(t);
}
private void handleWatchX509ContextError(final Throwable t) {
if (isErrorNotRetryable(t)) {
watcher.onError(new X509ContextException("Canceling X.509 Context watch", t));
watcher.onError(new X509ContextException("Cancelling X.509 Context watch", t));
} else {
handleX509ContextRetry(t);
}
@ -56,20 +61,74 @@ final class StreamObservers {
private void handleX509ContextRetry(Throwable t) {
if (retryHandler.shouldRetry()) {
log.log(Level.INFO, "Retrying connecting to Workload API to register X.509 context watcher");
log.log(Level.FINE, "Retrying connecting to Workload API to register X.509 context watcher");
retryHandler.scheduleRetry(() ->
cancellableContext.run(
() -> workloadApiAsyncStub.fetchX509SVID(newX509SvidRequest(),
this)));
} else {
watcher.onError(new X509ContextException("Canceling X.509 Context watch", t));
watcher.onError(new X509ContextException("Cancelling X.509 Context watch", t));
}
}
@Override
public void onCompleted() {
cancellableContext.close();
log.info("Workload API stream is completed");
log.info(STREAM_IS_COMPLETED);
}
};
}
static StreamObserver<Workload.X509BundlesResponse> getX509BundlesStreamObserver(
final Watcher<X509BundleSet> watcher,
final RetryHandler retryHandler,
final Context.CancellableContext cancellableContext,
final SpiffeWorkloadAPIGrpc.SpiffeWorkloadAPIStub workloadApiAsyncStub) {
return new StreamObserver<Workload.X509BundlesResponse>() {
@Override
public void onNext(final Workload.X509BundlesResponse value) {
try {
val x509Context = GrpcConversionUtils.toX509BundleSet(value);
watcher.onUpdate(x509Context);
retryHandler.reset();
} catch (X509BundleException e) {
watcher.onError(new X509ContextException("Error processing X.509 bundles update", e));
}
}
@Override
public void onError(final Throwable t) {
if (Status.fromThrowable(t).getCode() != Status.Code.CANCELLED) {
log.log(Level.SEVERE, "X.509 bundles observer error", t);
}
handleWatchX509BundlesError(t);
}
private void handleWatchX509BundlesError(final Throwable t) {
if (isErrorNotRetryable(t)) {
watcher.onError(new X509ContextException("Cancelling X.509 bundles watch", t));
} else {
handleX509BundlesRetry(t);
}
}
private void handleX509BundlesRetry(Throwable t) {
if (retryHandler.shouldRetry()) {
log.log(Level.FINE, "Retrying connecting to Workload API to register X.509 bundles watcher");
retryHandler.scheduleRetry(() ->
cancellableContext.run(
() -> workloadApiAsyncStub.fetchX509Bundles(newX509BundlesRequest(),
this)));
} else {
watcher.onError(new X509BundleException("Cancelling X.509 bundles watch", t));
}
}
@Override
public void onCompleted() {
cancellableContext.close();
log.info(STREAM_IS_COMPLETED);
}
};
}
@ -84,7 +143,7 @@ final class StreamObservers {
@Override
public void onNext(final Workload.JWTBundlesResponse value) {
try {
val jwtBundleSet = GrpcConversionUtils.toBundleSet(value);
val jwtBundleSet = GrpcConversionUtils.toJwtBundleSet(value);
watcher.onUpdate(jwtBundleSet);
retryHandler.reset();
} catch (JwtBundleException e) {
@ -94,13 +153,15 @@ final class StreamObservers {
@Override
public void onError(final Throwable t) {
log.log(Level.SEVERE, "JWT observer error", t);
if (Status.fromThrowable(t).getCode() != Status.Code.CANCELLED) {
log.log(Level.SEVERE, "JWT observer error", t);
}
handleWatchJwtBundleError(t);
}
private void handleWatchJwtBundleError(final Throwable t) {
if (isErrorNotRetryable(t)) {
watcher.onError(new JwtBundleException("Canceling JWT Bundles watch", t));
watcher.onError(new JwtBundleException("Cancelling JWT Bundles watch", t));
} else {
handleJwtBundleRetry(t);
}
@ -108,19 +169,19 @@ final class StreamObservers {
private void handleJwtBundleRetry(Throwable t) {
if (retryHandler.shouldRetry()) {
log.log(Level.INFO, "Retrying connecting to Workload API to register JWT Bundles watcher");
log.log(Level.FINE, "Retrying connecting to Workload API to register JWT Bundles watcher");
retryHandler.scheduleRetry(() ->
cancellableContext.run(() -> workloadApiAsyncStub.fetchJWTBundles(newJwtBundlesRequest(),
this)));
} else {
watcher.onError(new JwtBundleException("Canceling JWT Bundles watch", t));
watcher.onError(new JwtBundleException("Cancelling JWT Bundles watch", t));
}
}
@Override
public void onCompleted() {
cancellableContext.close();
log.info("Workload API stream is completed");
log.info(STREAM_IS_COMPLETED);
}
};
}
@ -133,6 +194,10 @@ final class StreamObservers {
return Workload.X509SVIDRequest.newBuilder().build();
}
private static Workload.X509BundlesRequest newX509BundlesRequest() {
return Workload.X509BundlesRequest.newBuilder().build();
}
private static Workload.JWTBundlesRequest newJwtBundlesRequest() {
return Workload.JWTBundlesRequest.newBuilder().build();
}

View File

@ -1,14 +1,17 @@
package io.spiffe.workloadapi;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.JwtBundleException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.exception.X509BundleException;
import io.spiffe.exception.X509ContextException;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.svid.jwtsvid.JwtSvid;
import lombok.NonNull;
import java.io.Closeable;
import java.util.List;
/**
* Represents a client to interact with the Workload API.
@ -36,6 +39,25 @@ public interface WorkloadApiClient extends Closeable {
*/
void watchX509Context(@NonNull Watcher<X509Context> watcher);
/**
* Fetches the X.509 bundles on a one-shot blocking call.
*
* @return an instance of a {@link X509BundleSet} containing the X.509 bundles keyed by TrustDomain
* @throws X509BundleException if there is an error fetching or processing the X.509 bundles
*/
X509BundleSet fetchX509Bundles() throws X509BundleException;
/**
* Watches for X.509 bundles updates.
* <p>
* A new Stream to the Workload API is opened for each call to this method, so that the client starts getting
* updates immediately after the Stream is ready and doesn't have to wait until the Workload API dispatches
* the next update.
*
* @param watcher an instance that implements a {@link Watcher} for {@link X509BundleSet}.
*/
void watchX509Bundles(@NonNull Watcher<X509BundleSet> watcher);
/**
* Fetches a SPIFFE JWT-SVID on one-shot blocking call.
*
@ -57,6 +79,27 @@ public interface WorkloadApiClient extends Closeable {
*/
JwtSvid fetchJwtSvid(@NonNull SpiffeId subject, @NonNull String audience, String... extraAudience) throws JwtSvidException;
/**
* Fetches all SPIFFE JWT-SVIDs on one-shot blocking call.
*
* @param audience the audience of the JWT-SVID
* @param extraAudience the extra audience for the JWT_SVID
* @return all of {@link JwtSvid} object
* @throws JwtSvidException if there is an error fetching or processing the JWT from the Workload API
*/
List<JwtSvid> fetchJwtSvids(@NonNull String audience, String... extraAudience) throws JwtSvidException;
/**
* Fetches a SPIFFE JWT-SVID on one-shot blocking call.
*
* @param subject a SPIFFE ID
* @param audience the audience of the JWT-SVID
* @param extraAudience the extra audience for the JWT_SVID
* @return all of {@link JwtSvid} objectÏ
* @throws JwtSvidException if there is an error fetching or processing the JWT from the Workload API
*/
List<JwtSvid> fetchJwtSvids(@NonNull SpiffeId subject, @NonNull String audience, String... extraAudience) throws JwtSvidException;
/**
* Fetches the JWT bundles for JWT-SVID validation, keyed by trust domain.
*

View File

@ -28,6 +28,10 @@ public class RetryHandler {
* @param runnable the task to be scheduled for execution
*/
public void scheduleRetry(final Runnable runnable) {
if (executor.isShutdown()) {
return;
}
if (exponentialBackoffPolicy.reachedMaxRetries(retryCount)) {
return;
}

View File

@ -4,92 +4,164 @@ option java_package = "io.spiffe.workloadapi.grpc";
import "google/protobuf/struct.proto";
service SpiffeWorkloadAPI {
/////////////////////////////////////////////////////////////////////////
// X509-SVID Profile
/////////////////////////////////////////////////////////////////////////
// Fetch X.509-SVIDs for all SPIFFE identities the workload is entitled to,
// as well as related information like trust bundles and CRLs. As this
// information changes, subsequent messages will be streamed from the
// server.
rpc FetchX509SVID(X509SVIDRequest) returns (stream X509SVIDResponse);
// Fetch trust bundles and CRLs. Useful for clients that only need to
// validate SVIDs without obtaining an SVID for themself. As this
// information changes, subsequent messages will be streamed from the
// server.
rpc FetchX509Bundles(X509BundlesRequest) returns (stream X509BundlesResponse);
/////////////////////////////////////////////////////////////////////////
// JWT-SVID Profile
/////////////////////////////////////////////////////////////////////////
// Fetch JWT-SVIDs for all SPIFFE identities the workload is entitled to,
// for the requested audience. If an optional SPIFFE ID is requested, only
// the JWT-SVID for that SPIFFE ID is returned.
rpc FetchJWTSVID(JWTSVIDRequest) returns (JWTSVIDResponse);
// Fetches the JWT bundles, formatted as JWKS documents, keyed by the
// SPIFFE ID of the trust domain. As this information changes, subsequent
// messages will be streamed from the server.
rpc FetchJWTBundles(JWTBundlesRequest) returns (stream JWTBundlesResponse);
// Validates a JWT-SVID against the requested audience. Returns the SPIFFE
// ID of the JWT-SVID and JWT claims.
rpc ValidateJWTSVID(ValidateJWTSVIDRequest) returns (ValidateJWTSVIDResponse);
}
// The X509SVIDRequest message conveys parameters for requesting an X.509-SVID.
// There are currently no request parameters.
message X509SVIDRequest { }
// The X509SVIDResponse message carries a set of X.509 SVIDs and their
// associated information. It also carries a set of global CRLs, and a
// TTL to inform the workload when it should check back next.
// The X509SVIDResponse message carries X.509-SVIDs and related information,
// including a set of global CRLs and a list of bundles the workload may use
// for federating with foreign trust domains.
message X509SVIDResponse {
// A list of X509SVID messages, each of which includes a single
// SPIFFE Verifiable Identity Document, along with its private key
// and bundle.
// Required. A list of X509SVID messages, each of which includes a single
// X.509-SVID, its private key, and the bundle for the trust domain.
repeated X509SVID svids = 1;
// ASN.1 DER encoded
// Optional. ASN.1 DER encoded certificate revocation lists.
repeated bytes crl = 2;
// CA certificate bundles belonging to foreign Trust Domains that the
// workload should trust, keyed by the SPIFFE ID of the foreign
// Optional. CA certificate bundles belonging to foreign trust domains that
// the workload should trust, keyed by the SPIFFE ID of the foreign trust
// domain. Bundles are ASN.1 DER encoded.
map<string, bytes> federated_bundles = 3;
}
// The X509SVID message carries a single SVID and all associated
// information, including CA bundles.
// The X509SVID message carries a single SVID and all associated information,
// including the X.509 bundle for the trust domain.
message X509SVID {
// The SPIFFE ID of the SVID in this entry
// Required. The SPIFFE ID of the SVID in this entry
string spiffe_id = 1;
// ASN.1 DER encoded certificate chain. MAY include intermediates,
// the leaf certificate (or SVID itself) MUST come first.
// Required. ASN.1 DER encoded certificate chain. MAY include
// intermediates, the leaf certificate (or SVID itself) MUST come first.
bytes x509_svid = 2;
// ASN.1 DER encoded PKCS#8 private key. MUST be unencrypted.
// Required. ASN.1 DER encoded PKCS#8 private key. MUST be unencrypted.
bytes x509_svid_key = 3;
// CA certificates belonging to the Trust Domain
// ASN.1 DER encoded
// Required. ASN.1 DER encoded X.509 bundle for the trust domain.
bytes bundle = 4;
// Optional. An operator-specified string used to provide guidance on how this
// identity should be used by a workload when more than one SVID is returned.
// For example, `internal` and `external` to indicate an SVID for internal or
// external use, respectively.
string hint = 5;
}
message JWTSVID {
string spiffe_id = 1;
// The X509BundlesRequest message conveys parameters for requesting X.509
// bundles. There are currently no such parameters.
message X509BundlesRequest {
}
// Encoded using JWS Compact Serialization
string svid = 2;
// The X509BundlesResponse message carries a set of global CRLs and a map of
// trust bundles the workload should trust.
message X509BundlesResponse {
// Optional. ASN.1 DER encoded certificate revocation lists.
repeated bytes crl = 1;
// Required. CA certificate bundles belonging to trust domains that the
// workload should trust, keyed by the SPIFFE ID of the trust domain.
// Bundles are ASN.1 DER encoded.
map<string, bytes> bundles = 2;
}
message JWTSVIDRequest {
// Required. The audience(s) the workload intends to authenticate against.
repeated string audience = 1;
// SPIFFE ID of the JWT being requested
// If not set, all IDs will be returned
// Optional. The requested SPIFFE ID for the JWT-SVID. If unset, all
// JWT-SVIDs to which the workload is entitled are requested.
string spiffe_id = 2;
}
// The JWTSVIDResponse message conveys JWT-SVIDs.
message JWTSVIDResponse {
// Required. The list of returned JWT-SVIDs.
repeated JWTSVID svids = 1;
}
// The JWTSVID message carries the JWT-SVID token and associated metadata.
message JWTSVID {
// Required. The SPIFFE ID of the JWT-SVID.
string spiffe_id = 1;
// Required. Encoded JWT using JWS Compact Serialization.
string svid = 2;
// Optional. An operator-specified string used to provide guidance on how this
// identity should be used by a workload when more than one SVID is returned.
// For example, `internal` and `external` to indicate an SVID for internal or
// external use, respectively.
string hint = 3;
}
// The JWTBundlesRequest message conveys parameters for requesting JWT bundles.
// There are currently no such parameters.
message JWTBundlesRequest { }
// The JWTBundlesReponse conveys JWT bundles.
message JWTBundlesResponse {
// JWK sets, keyed by trust domain URI
// Required. JWK encoded JWT bundles, keyed by the SPIFFE ID of the trust
// domain.
map<string, bytes> bundles = 1;
}
// The ValidateJWTSVIDRequest message conveys request parameters for
// JWT-SVID validation.
message ValidateJWTSVIDRequest {
// Required. The audience of the validating party. The JWT-SVID must
// contain an audience claim which contains this value in order to
// succesfully validate.
string audience = 1;
// Encoded using JWS Compact Serialization
// Required. The JWT-SVID to validate, encoded using JWS Compact
// Serialization.
string svid = 2;
}
// The ValidateJWTSVIDReponse message conveys the JWT-SVID validation results.
message ValidateJWTSVIDResponse {
// Required. The SPIFFE ID of the validated JWT-SVID.
string spiffe_id = 1;
// Optional. Arbitrary claims contained within the payload of the validated
// JWT-SVID.
google.protobuf.Struct claims = 2;
}
service SpiffeWorkloadAPI {
// JWT-SVID Profile
rpc FetchJWTSVID(JWTSVIDRequest) returns (JWTSVIDResponse);
rpc FetchJWTBundles(JWTBundlesRequest) returns (stream JWTBundlesResponse);
rpc ValidateJWTSVID(ValidateJWTSVIDRequest) returns (ValidateJWTSVIDResponse);
// X.509-SVID Profile
// Fetch all SPIFFE identities the workload is entitled to, as
// well as related information like trust bundles and CRLs. As
// this information changes, subsequent messages will be sent.
rpc FetchX509SVID(X509SVIDRequest) returns (stream X509SVIDResponse);
}

View File

@ -19,8 +19,8 @@ class JwtBundleSetTest {
@Test
void testOfListOfBundles() {
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle2 = new JwtBundle(TrustDomain.of("other.org"));
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.parse("example.org"));
JwtBundle jwtBundle2 = new JwtBundle(TrustDomain.parse("other.org"));
List<JwtBundle> bundles = Arrays.asList(jwtBundle1, jwtBundle2);
@ -28,20 +28,20 @@ class JwtBundleSetTest {
assertNotNull(bundleSet);
assertEquals(2, bundleSet.getBundles().size());
assertEquals(jwtBundle1, bundleSet.getBundles().get(TrustDomain.of("example.org")));
assertEquals(jwtBundle2, bundleSet.getBundles().get(TrustDomain.of("other.org")));
assertEquals(jwtBundle1, bundleSet.getBundles().get(TrustDomain.parse("example.org")));
assertEquals(jwtBundle2, bundleSet.getBundles().get(TrustDomain.parse("other.org")));
}
@Test
void getBundleForTrustDomain_Success() {
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle2 = new JwtBundle(TrustDomain.of("other.org"));
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.parse("example.org"));
JwtBundle jwtBundle2 = new JwtBundle(TrustDomain.parse("other.org"));
List<JwtBundle> bundles = Arrays.asList(jwtBundle1, jwtBundle2);
JwtBundleSet bundleSet = JwtBundleSet.of(bundles);
JwtBundle bundle = null;
try {
bundle = bundleSet.getBundleForTrustDomain(TrustDomain.of("example.org"));
bundle = bundleSet.getBundleForTrustDomain(TrustDomain.parse("example.org"));
} catch (BundleNotFoundException e) {
fail(e);
}
@ -78,13 +78,13 @@ class JwtBundleSetTest {
@Test
void testgetBundleForTrustDomain_TrustDomainNotInSet_ThrowsBundleNotFoundException() {
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle2 = new JwtBundle(TrustDomain.of("other.org"));
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.parse("example.org"));
JwtBundle jwtBundle2 = new JwtBundle(TrustDomain.parse("other.org"));
List<JwtBundle> bundles = Arrays.asList(jwtBundle1, jwtBundle2);
JwtBundleSet bundleSet = JwtBundleSet.of(bundles);
try {
bundleSet.getBundleForTrustDomain(TrustDomain.of("domain.test"));
bundleSet.getBundleForTrustDomain(TrustDomain.parse("domain.test"));
fail("exception expected");
} catch (BundleNotFoundException e) {
assertEquals("No JWT bundle for trust domain domain.test", e.getMessage());
@ -93,7 +93,7 @@ class JwtBundleSetTest {
@Test
void testgetBundleForTrustDomain_null_throwsNullPointerException() throws BundleNotFoundException {
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.parse("example.org"));
List<JwtBundle> bundleList = Collections.singletonList(jwtBundle1);
JwtBundleSet bundleSet = JwtBundleSet.of(bundleList);
try {
@ -105,11 +105,11 @@ class JwtBundleSetTest {
@Test
void testAdd() {
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.parse("example.org"));
List<JwtBundle> bundleList = Collections.singletonList(jwtBundle1);
JwtBundleSet bundleSet = JwtBundleSet.of(bundleList);
JwtBundle jwtBundle2 = new JwtBundle(TrustDomain.of("other.org"));
JwtBundle jwtBundle2 = new JwtBundle(TrustDomain.parse("other.org"));
bundleSet.put(jwtBundle2);
assertTrue(bundleSet.getBundles().containsValue(jwtBundle1));
@ -118,7 +118,7 @@ class JwtBundleSetTest {
@Test
void testAdd_sameBundleAgain_noDuplicate() {
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.parse("example.org"));
List<JwtBundle> bundleList = Collections.singletonList(jwtBundle1);
JwtBundleSet bundleSet = JwtBundleSet.of(bundleList);
@ -130,11 +130,11 @@ class JwtBundleSetTest {
@Test
void testAdd_aDifferentBundleForSameTrustDomain_replacesWithNewBundle() {
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.parse("example.org"));
List<JwtBundle> bundleList = Collections.singletonList(jwtBundle1);
JwtBundleSet bundleSet = JwtBundleSet.of(bundleList);
JwtBundle jwtBundle2 = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle2 = new JwtBundle(TrustDomain.parse("example.org"));
jwtBundle2.putJwtAuthority("key1", new DummyPublicKey());
bundleSet.put(jwtBundle2);
@ -144,7 +144,7 @@ class JwtBundleSetTest {
@Test
void add_null_throwsNullPointerException() {
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle1 = new JwtBundle(TrustDomain.parse("example.org"));
List<JwtBundle> bundleList = Collections.singletonList(jwtBundle1);
JwtBundleSet bundleSet = JwtBundleSet.of(bundleList);
try {

View File

@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -30,9 +31,9 @@ class JwtBundleTest {
@Test
void testNewJwtBundleWithTrustDomain_Success() {
JwtBundle jwtBundle = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle = new JwtBundle(TrustDomain.parse("example.org"));
assertNotNull(jwtBundle);
assertEquals(TrustDomain.of("example.org"), jwtBundle.getTrustDomain());
assertEquals(TrustDomain.parse("example.org"), jwtBundle.getTrustDomain());
}
@Test
@ -45,13 +46,13 @@ class JwtBundleTest {
authorities.put("authority1", key1.getPublic());
authorities.put("authority2", key2.getPublic());
JwtBundle jwtBundle = new JwtBundle(TrustDomain.of("example.org"), authorities);
JwtBundle jwtBundle = new JwtBundle(TrustDomain.parse("example.org"), authorities);
// change a key in the map, to test that the bundle has its own copy
authorities.put("authority1", key2.getPublic());
assertNotNull(jwtBundle);
assertEquals(TrustDomain.of("example.org"), jwtBundle.getTrustDomain());
assertEquals(TrustDomain.parse("example.org"), jwtBundle.getTrustDomain());
assertEquals(2, jwtBundle.getJwtAuthorities().size());
assertEquals(key1.getPublic(), jwtBundle.getJwtAuthorities().get("authority1"));
assertEquals(key2.getPublic(), jwtBundle.getJwtAuthorities().get("authority2"));
@ -71,7 +72,7 @@ class JwtBundleTest {
@Test
void testNewJwtBundleWithTrustDomain_AuthoritiesIsNull_ThrowsNullPointerException() {
try {
new JwtBundle(TrustDomain.of("example.org"), null);
new JwtBundle(TrustDomain.parse("example.org"), null);
fail("NullPointerException was expected");
} catch (NullPointerException e) {
assertEquals("jwtAuthorities is marked non-null but is null", e.getMessage());
@ -91,7 +92,7 @@ class JwtBundleTest {
@Test
void testLoadFileWithEcKey_Success() throws URISyntaxException {
Path path = Paths.get(toUri("testdata/jwtbundle/jwks_valid_EC_1.json"));
TrustDomain trustDomain = TrustDomain.of("example.org");
TrustDomain trustDomain = TrustDomain.parse("example.org");
JwtBundle jwtBundle = null;
try {
@ -101,7 +102,7 @@ class JwtBundleTest {
}
assertNotNull(jwtBundle);
assertEquals(TrustDomain.of("example.org"), jwtBundle.getTrustDomain());
assertEquals(TrustDomain.parse("example.org"), jwtBundle.getTrustDomain());
assertEquals(1, jwtBundle.getJwtAuthorities().size());
assertNotNull(jwtBundle.getJwtAuthorities().get("C6vs25welZOx6WksNYfbMfiw9l96pMnD"));
}
@ -109,7 +110,7 @@ class JwtBundleTest {
@Test
void testLoadFileWithRsaKey_Success() throws URISyntaxException {
Path path = Paths.get(toUri("testdata/jwtbundle/jwks_valid_RSA_1.json"));
TrustDomain trustDomain = TrustDomain.of("domain.test");
TrustDomain trustDomain = TrustDomain.parse("domain.test");
JwtBundle jwtBundle = null;
try {
@ -119,7 +120,7 @@ class JwtBundleTest {
}
assertNotNull(jwtBundle);
assertEquals(TrustDomain.of("domain.test"), jwtBundle.getTrustDomain());
assertEquals(TrustDomain.parse("domain.test"), jwtBundle.getTrustDomain());
assertEquals(1, jwtBundle.getJwtAuthorities().size());
assertNotNull(jwtBundle.getJwtAuthorities().get("14cc39cd-838d-426d-9bb1-77f3468fba96"));
}
@ -127,7 +128,7 @@ class JwtBundleTest {
@Test
void testLoadFileWithRsaAndEc_Success() throws URISyntaxException {
Path path = Paths.get(toUri("testdata/jwtbundle/jwks_valid_RSA_EC.json"));
TrustDomain trustDomain = TrustDomain.of("domain.test");
TrustDomain trustDomain = TrustDomain.parse("domain.test");
JwtBundle jwtBundle = null;
try {
@ -137,7 +138,7 @@ class JwtBundleTest {
}
assertNotNull(jwtBundle);
assertEquals(TrustDomain.of("domain.test"), jwtBundle.getTrustDomain());
assertEquals(TrustDomain.parse("domain.test"), jwtBundle.getTrustDomain());
assertEquals(2, jwtBundle.getJwtAuthorities().size());
assertNotNull(jwtBundle.getJwtAuthorities().get("14cc39cd-838d-426d-9bb1-77f3468fba96"));
assertNotNull(jwtBundle.getJwtAuthorities().get("C6vs25welZOx6WksNYfbMfiw9l96pMnD"));
@ -146,7 +147,7 @@ class JwtBundleTest {
@Test
void testLoadFile_MissingKid_ThrowsJwtBundleException() throws URISyntaxException, KeyException {
Path path = Paths.get(toUri("testdata/jwtbundle/jwks_missing_kid.json"));
TrustDomain trustDomain = TrustDomain.of("domain.test");
TrustDomain trustDomain = TrustDomain.parse("domain.test");
try {
JwtBundle.load(trustDomain, path);
@ -157,22 +158,22 @@ class JwtBundleTest {
}
@Test
void testLoadFile_InvalidKeyType_ThrowsKeyException() throws URISyntaxException, JwtBundleException {
void testLoadFile_InvalidKeyType_ThrowsKeyException() throws URISyntaxException, KeyException {
Path path = Paths.get(toUri("testdata/jwtbundle/jwks_invalid_keytype.json"));
TrustDomain trustDomain = TrustDomain.of("domain.test");
TrustDomain trustDomain = TrustDomain.parse("domain.test");
try {
JwtBundle.load(trustDomain, path);
fail("should have thrown exception");
} catch (KeyException e) {
assertEquals("Key Type not supported: OKP", e.getMessage());
} catch (JwtBundleException e) {
assertEquals("Unsupported JWT family algorithm: OKP", e.getCause().getMessage());
}
}
@Test
void testLoadFile_NonExistentFile_ThrowsException() throws KeyException {
Path path = Paths.get("testdata/jwtbundle/non-existen.json");
TrustDomain trustDomain = TrustDomain.of("domain.test");
TrustDomain trustDomain = TrustDomain.parse("domain.test");
try {
JwtBundle.load(trustDomain, path);
@ -194,7 +195,7 @@ class JwtBundleTest {
@Test
void testLoad_NullBundlePath_ThrowsNullPointerException() throws KeyException, JwtBundleException {
try {
JwtBundle.load(TrustDomain.of("example.org"), null);
JwtBundle.load(TrustDomain.parse("example.org"), null);
} catch (NullPointerException e) {
assertEquals("bundlePath is marked non-null but is null", e.getMessage());
}
@ -207,23 +208,42 @@ class JwtBundleTest {
JwtBundle jwtBundle = null;
try {
jwtBundle = JwtBundle.parse(TrustDomain.of("domain.test"), bundleBytes);
jwtBundle = JwtBundle.parse(TrustDomain.parse("domain.test"), bundleBytes);
} catch (JwtBundleException e) {
fail(e);
}
assertNotNull(jwtBundle);
assertEquals(TrustDomain.of("domain.test"), jwtBundle.getTrustDomain());
assertEquals(TrustDomain.parse("domain.test"), jwtBundle.getTrustDomain());
assertEquals(2, jwtBundle.getJwtAuthorities().size());
assertNotNull(jwtBundle.getJwtAuthorities().get("14cc39cd-838d-426d-9bb1-77f3468fba96"));
assertNotNull(jwtBundle.getJwtAuthorities().get("C6vs25welZOx6WksNYfbMfiw9l96pMnD"));
}
@Test
void testParseJWKSWithEmptyKeysArray_Success() {
TrustDomain trustDomain = TrustDomain.parse("example.org");
String jwksEmptyKeysJson = "{\"keys\": []}";
byte[] bundleBytes = jwksEmptyKeysJson.getBytes(StandardCharsets.UTF_8);
JwtBundle jwtBundle = null;
try {
jwtBundle = JwtBundle.parse(trustDomain, bundleBytes);
} catch (JwtBundleException e) {
fail("Parsing failed with exception: " + e.getMessage());
}
assertNotNull(jwtBundle, "JwtBundle should not be null");
assertEquals(trustDomain, jwtBundle.getTrustDomain(), "Trust domain should match");
assertTrue(jwtBundle.getJwtAuthorities().isEmpty(), "JwtAuthorities should be empty");
}
@Test
void testParse_MissingKid_Fails() throws URISyntaxException, IOException {
Path path = Paths.get(toUri("testdata/jwtbundle/jwks_missing_kid.json"));
byte[] bundleBytes = Files.readAllBytes(path);
TrustDomain trustDomain = TrustDomain.of("domain.test");
TrustDomain trustDomain = TrustDomain.parse("domain.test");
try {
JwtBundle.parse(trustDomain, bundleBytes);
@ -236,7 +256,7 @@ class JwtBundleTest {
@Test
void testParseInvalidJson() throws KeyException {
try {
JwtBundle.parse(TrustDomain.of("example.org"), "invalid json".getBytes());
JwtBundle.parse(TrustDomain.parse("example.org"), "invalid json".getBytes());
fail("exception is expected");
} catch (JwtBundleException e) {
assertEquals("Could not parse bundle from bytes", e.getMessage());
@ -255,7 +275,7 @@ class JwtBundleTest {
@Test
void testParse_NullBundleBytes_ThrowsNullPointerException() throws KeyException, JwtBundleException {
try {
JwtBundle.parse(TrustDomain.of("example.org"), null);
JwtBundle.parse(TrustDomain.parse("example.org"), null);
} catch (NullPointerException e) {
assertEquals("bundleBytes is marked non-null but is null", e.getMessage());
}
@ -264,20 +284,20 @@ class JwtBundleTest {
@Test
void testgetBundleForTrustDomain_Success() {
JwtBundle jwtBundle = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle = new JwtBundle(TrustDomain.parse("example.org"));
try {
JwtBundle bundle = jwtBundle.getBundleForTrustDomain(TrustDomain.of("example.org"));
JwtBundle bundle = jwtBundle.getBundleForTrustDomain(TrustDomain.parse("example.org"));
assertEquals(jwtBundle, bundle);
} catch (BundleNotFoundException e) {
fail(e);
fail(e);
}
}
@Test
void testgetBundleForTrustDomain_doesNotExiste_ThrowsBundleNotFoundException() {
JwtBundle jwtBundle = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle = new JwtBundle(TrustDomain.parse("example.org"));
try {
jwtBundle.getBundleForTrustDomain(TrustDomain.of("other.org"));
jwtBundle.getBundleForTrustDomain(TrustDomain.parse("other.org"));
fail("exception expected");
} catch (BundleNotFoundException e) {
assertEquals("No JWT bundle found for trust domain other.org", e.getMessage());
@ -286,7 +306,7 @@ class JwtBundleTest {
@Test
void testJWTAuthoritiesCRUD() {
JwtBundle jwtBundle = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle = new JwtBundle(TrustDomain.parse("example.org"));
// Test addJWTAuthority
DummyPublicKey jwtAuthority1 = new DummyPublicKey();
@ -305,8 +325,8 @@ class JwtBundleTest {
} catch (AuthorityNotFoundException e) {
fail(e);
}
assertEquals(key1, jwtAuthority1 );
assertEquals(key2, jwtAuthority2 );
assertEquals(key1, jwtAuthority1);
assertEquals(key2, jwtAuthority2);
// Test RemoveJwtAuthority
jwtBundle.removeJwtAuthority("key1");
@ -321,7 +341,7 @@ class JwtBundleTest {
@Test
void testAddJwtAuthority_emtpyKeyId_throwsIllegalArgumentException() {
JwtBundle jwtBundle = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle = new JwtBundle(TrustDomain.parse("example.org"));
try {
jwtBundle.putJwtAuthority("", new DummyPublicKey());
} catch (IllegalArgumentException e) {
@ -331,7 +351,7 @@ class JwtBundleTest {
@Test
void testAddJwtAuthority_nullKeyId_throwsNullPointerException() {
JwtBundle jwtBundle = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle = new JwtBundle(TrustDomain.parse("example.org"));
try {
jwtBundle.putJwtAuthority(null, new DummyPublicKey());
} catch (NullPointerException e) {
@ -341,7 +361,7 @@ class JwtBundleTest {
@Test
void testAddJwtAuthority_nullJwtAuthority_throwsNullPointerException() {
JwtBundle jwtBundle = new JwtBundle(TrustDomain.of("example.org"));
JwtBundle jwtBundle = new JwtBundle(TrustDomain.parse("example.org"));
try {
jwtBundle.putJwtAuthority("key1", null);
} catch (NullPointerException e) {

View File

@ -19,8 +19,8 @@ class X509BundleSetTest {
@Test
void testOf_listOfBundles_Success() {
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.of("other.org"));
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.parse("example.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.parse("other.org"));
List<X509Bundle> bundleList = Arrays.asList(x509Bundle1, x509Bundle2);
X509BundleSet bundleSet = X509BundleSet.of(bundleList);
@ -57,11 +57,11 @@ class X509BundleSetTest {
@Test
void testAdd() {
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.parse("example.org"));
List<X509Bundle> bundleList = Collections.singletonList(x509Bundle1);
X509BundleSet bundleSet = X509BundleSet.of(bundleList);
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.of("other.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.parse("other.org"));
bundleSet.put(x509Bundle2);
assertTrue(bundleSet.getBundles().containsValue(x509Bundle1));
@ -70,7 +70,7 @@ class X509BundleSetTest {
@Test
void testAdd_sameBundleAgain_noDuplicate() {
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.parse("example.org"));
List<X509Bundle> bundleList = Collections.singletonList(x509Bundle1);
X509BundleSet bundleSet = X509BundleSet.of(bundleList);
@ -82,11 +82,11 @@ class X509BundleSetTest {
@Test
void testAdd_aDifferentBundleForSameTrustDomain_replacesWithNewBundle() {
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.parse("example.org"));
List<X509Bundle> bundleList = Collections.singletonList(x509Bundle1);
X509BundleSet bundleSet = X509BundleSet.of(bundleList);
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.parse("example.org"));
x509Bundle2.addX509Authority(new DummyX509Certificate());
bundleSet.put(x509Bundle2);
@ -97,7 +97,7 @@ class X509BundleSetTest {
@Test
void testAdd_nullBundle_throwsNullPointerException() {
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.parse("example.org"));
List<X509Bundle> bundleList = Collections.singletonList(x509Bundle1);
X509BundleSet bundleSet = X509BundleSet.of(bundleList);
@ -111,24 +111,24 @@ class X509BundleSetTest {
@Test
void testgetBundleForTrustDomain_Success() throws BundleNotFoundException {
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.of("other.org"));
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.parse("example.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.parse("other.org"));
List<X509Bundle> bundleList = Arrays.asList(x509Bundle1, x509Bundle2);
X509BundleSet bundleSet = X509BundleSet.of(bundleList);
assertEquals(x509Bundle1, bundleSet.getBundleForTrustDomain(TrustDomain.of("example.org")));
assertEquals(x509Bundle2, bundleSet.getBundleForTrustDomain(TrustDomain.of("other.org")));
assertEquals(x509Bundle1, bundleSet.getBundleForTrustDomain(TrustDomain.parse("example.org")));
assertEquals(x509Bundle2, bundleSet.getBundleForTrustDomain(TrustDomain.parse("other.org")));
}
@Test
void testgetBundleForTrustDomain_notFoundTrustDomain() {
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.of("other.org"));
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.parse("example.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.parse("other.org"));
List<X509Bundle> bundleList = Arrays.asList(x509Bundle1, x509Bundle2);
X509BundleSet bundleSet = X509BundleSet.of(bundleList);
try {
bundleSet.getBundleForTrustDomain(TrustDomain.of("unknown.org"));
bundleSet.getBundleForTrustDomain(TrustDomain.parse("unknown.org"));
fail("expected BundleNotFoundException");
} catch (BundleNotFoundException e) {
assertEquals("No X.509 bundle for trust domain unknown.org", e.getMessage());
@ -137,8 +137,8 @@ class X509BundleSetTest {
@Test
void testgetBundleForTrustDomain_nullTrustDomain_throwsException() throws BundleNotFoundException {
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.of("other.org"));
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.parse("example.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.parse("other.org"));
List<X509Bundle> bundleList = Arrays.asList(x509Bundle1, x509Bundle2);
X509BundleSet bundleSet = X509BundleSet.of(bundleList);

View File

@ -31,9 +31,9 @@ public class X509BundleTest {
@Test
void TestNewBundle() {
X509Bundle x509Bundle = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle = new X509Bundle(TrustDomain.parse("example.org"));
assertEquals(0, x509Bundle.getX509Authorities().size());
assertEquals(TrustDomain.of("example.org"), x509Bundle.getTrustDomain());
assertEquals(TrustDomain.parse("example.org"), x509Bundle.getTrustDomain());
}
@Test
@ -59,7 +59,7 @@ public class X509BundleTest {
@Test
void testNewBundleAuthorities_nullAuthorities_throwsNullPointerException() {
try {
new X509Bundle(TrustDomain.of("example.org"), null);
new X509Bundle(TrustDomain.parse("example.org"), null);
fail("should have thrown exception");
} catch (NullPointerException e) {
assertEquals("x509Authorities is marked non-null but is null", e.getMessage());
@ -75,22 +75,22 @@ public class X509BundleTest {
authorities.add(x509Cert1);
authorities.add(x509Cert2);
X509Bundle x509Bundle = new X509Bundle(TrustDomain.of("example.org"), authorities);
X509Bundle x509Bundle = new X509Bundle(TrustDomain.parse("example.org"), authorities);
assertEquals(authorities, x509Bundle.getX509Authorities());
}
@Test
void testGetBundleForTrustDomain() throws BundleNotFoundException {
X509Bundle x509Bundle = new X509Bundle(TrustDomain.of("example.org"));
assertEquals(x509Bundle, x509Bundle.getBundleForTrustDomain(TrustDomain.of("example.org")));
X509Bundle x509Bundle = new X509Bundle(TrustDomain.parse("example.org"));
assertEquals(x509Bundle, x509Bundle.getBundleForTrustDomain(TrustDomain.parse("example.org")));
}
@Test
void testGetBundleForTrustDomain_notBundleFound_throwsBundleNotFoundException() {
X509Bundle x509Bundle = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle = new X509Bundle(TrustDomain.parse("example.org"));
try {
x509Bundle.getBundleForTrustDomain(TrustDomain.of("other.org"));
x509Bundle.getBundleForTrustDomain(TrustDomain.parse("other.org"));
} catch (BundleNotFoundException e) {
assertEquals("No X.509 bundle found for trust domain other.org", e.getMessage());
}
@ -98,7 +98,7 @@ public class X509BundleTest {
@Test
void testGetBundleForTrustDomain_nullArgument_throwsNullPointerException() {
X509Bundle x509Bundle = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle = new X509Bundle(TrustDomain.parse("example.org"));
try {
x509Bundle.getBundleForTrustDomain(null);
fail();
@ -113,7 +113,7 @@ public class X509BundleTest {
@Test
void TestLoad_Succeeds() {
try {
X509Bundle x509Bundle = X509Bundle.load(TrustDomain.of("example.org"), Paths.get(toUri("testdata/x509bundle/certs.pem")));
X509Bundle x509Bundle = X509Bundle.load(TrustDomain.parse("example.org"), Paths.get(toUri("testdata/x509bundle/certs.pem")));
assertEquals(2, x509Bundle.getX509Authorities().size());
} catch (URISyntaxException | X509BundleException e) {
fail(e);
@ -123,7 +123,7 @@ public class X509BundleTest {
@Test
void TestLoad_Fails() {
try {
X509Bundle.load(TrustDomain.of("example.org"), Paths.get("testdata/x509bundle/non-existent.pem"));
X509Bundle.load(TrustDomain.parse("example.org"), Paths.get("testdata/x509bundle/non-existent.pem"));
fail("should have thrown exception");
} catch (X509BundleException e) {
assertEquals("Unable to load X.509 bundle file", e.getMessage());
@ -143,7 +143,7 @@ public class X509BundleTest {
@Test
void testLoad_nullBundlePath_throwsNullPointerException() throws X509BundleException {
try {
X509Bundle.load(TrustDomain.of("example.org"), null);
X509Bundle.load(TrustDomain.parse("example.org"), null);
fail("should have thrown exception");
} catch (NullPointerException e) {
assertEquals("bundlePath is marked non-null but is null", e.getMessage());
@ -163,7 +163,7 @@ public class X509BundleTest {
@Test
void testParse_nullBundlePath_throwsNullPointerException() throws X509BundleException {
try {
X509Bundle.parse(TrustDomain.of("example.org"), null);
X509Bundle.parse(TrustDomain.parse("example.org"), null);
fail("should have thrown exception");
} catch (NullPointerException e) {
assertEquals("bundleBytes is marked non-null but is null", e.getMessage());
@ -172,7 +172,7 @@ public class X509BundleTest {
@Test
void testHasAuthority_nullArgument_throwsNullPointerException() {
X509Bundle x509Bundle = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle = new X509Bundle(TrustDomain.parse("example.org"));
try {
x509Bundle.hasX509Authority(null);
fail();
@ -183,7 +183,7 @@ public class X509BundleTest {
@Test
void testAddAuthority_nullArgument_throwsNullPointerException() {
X509Bundle x509Bundle = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle = new X509Bundle(TrustDomain.parse("example.org"));
try {
x509Bundle.addX509Authority(null);
fail();
@ -194,7 +194,7 @@ public class X509BundleTest {
@Test
void testRemoveAuthority_nullArgument_throwsNullPointerException() {
X509Bundle x509Bundle = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle = new X509Bundle(TrustDomain.parse("example.org"));
try {
x509Bundle.removeX509Authority(null);
fail();
@ -209,11 +209,11 @@ public class X509BundleTest {
X509Bundle bundle2 = null;
try {
// Load bundle1, which contains a single certificate
bundle1 = X509Bundle.load(TrustDomain.of("example.org"), Paths.get(toUri("testdata/x509bundle/cert.pem")));
bundle1 = X509Bundle.load(TrustDomain.parse("example.org"), Paths.get(toUri("testdata/x509bundle/cert.pem")));
// Load bundle2, which contains 2 certificates
// The first certificate is the same than the one used in bundle1
bundle2 = X509Bundle.load(TrustDomain.of("example.org"), Paths.get(toUri("testdata/x509bundle/certs.pem")));
bundle2 = X509Bundle.load(TrustDomain.parse("example.org"), Paths.get(toUri("testdata/x509bundle/certs.pem")));
} catch (URISyntaxException | X509BundleException e) {
fail(e);
}
@ -277,7 +277,7 @@ public class X509BundleTest {
.builder()
.name("Parse multiple certificates should succeed")
.path("testdata/x509bundle/certs.pem")
.trustDomain(TrustDomain.of("example.org"))
.trustDomain(TrustDomain.parse("example.org"))
.expectedNumberOfAuthorities(2)
.build()
),
@ -285,7 +285,7 @@ public class X509BundleTest {
.builder()
.name("Parse single certificate should succeed")
.path("testdata/x509bundle/cert.pem")
.trustDomain(TrustDomain.of("example.org"))
.trustDomain(TrustDomain.parse("example.org"))
.expectedNumberOfAuthorities(1)
.build()
),
@ -293,7 +293,7 @@ public class X509BundleTest {
.builder()
.name("Parse empty bytes should fail")
.path("testdata/x509bundle/empty.pem")
.trustDomain(TrustDomain.of("example.org"))
.trustDomain(TrustDomain.parse("example.org"))
.expectedError("Bundle certificates could not be parsed from bundle path")
.build()
),
@ -301,7 +301,7 @@ public class X509BundleTest {
.builder()
.name("Parse non-PEM bytes should fail")
.path("testdata/x509bundle/not-pem.pem")
.trustDomain(TrustDomain.of("example.org"))
.trustDomain(TrustDomain.parse("example.org"))
.expectedError("Bundle certificates could not be parsed from bundle path")
.build()
),
@ -309,7 +309,7 @@ public class X509BundleTest {
.builder()
.name("Parse should fail if no certificate block is is found")
.path("testdata/x509bundle/key.pem")
.trustDomain(TrustDomain.of("example.org"))
.trustDomain(TrustDomain.parse("example.org"))
.expectedError("Bundle certificates could not be parsed from bundle path")
.build()
),
@ -317,7 +317,7 @@ public class X509BundleTest {
.builder()
.name("Parse a corrupted certificate should fail")
.path("testdata/x509bundle/corrupted.pem")
.trustDomain(TrustDomain.of("example.org"))
.trustDomain(TrustDomain.parse("example.org"))
.expectedError("Bundle certificates could not be parsed from bundle path")
.build()
)

View File

@ -189,7 +189,7 @@ public class CertificateUtilsTest {
try {
TrustDomain trustDomain = CertificateUtils.getTrustDomain(chain);
assertNotNull(trustDomain);
assertEquals(TrustDomain.of("domain.test"), trustDomain);
assertEquals(TrustDomain.parse("domain.test"), trustDomain);
} catch (CertificateException e) {
fail(e);
}

View File

@ -1,194 +1,236 @@
package io.spiffe.spiffeid;
import com.google.common.collect.Sets;
import io.spiffe.exception.InvalidSpiffeIdException;
import lombok.val;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class SpiffeIdTest {
class SpiffeIdTest {
@Test
void of_TrustDomainAndPathSegments_ReturnsSpiffeIdWithTrustDomainAndPathWithSegments() {
val trustDomain = TrustDomain.of("trust-domain.org");
private static final Set<Character> LOWER_ALPHA = Sets.newHashSet('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
private static final Set<Character> UPPER_ALPHA = Sets.newHashSet('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z');
private static final Set<Character> NUMBERS = Sets.newHashSet('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
private static final Set<Character> SPECIAL_CHARS = Sets.newHashSet('.', '-', '_');
val spiffeId = SpiffeId.of(trustDomain, "path1", "path2");
static final Set<Character> TD_CHARS = Stream.of(
LOWER_ALPHA,
NUMBERS,
SPECIAL_CHARS)
.flatMap(Set::stream)
.collect(Collectors.toSet());
assertAll("spiffeId",
() -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()),
() -> assertEquals("/path1/path2", spiffeId.getPath())
);
}
@Test
void of_TrustDomainAndNoPaths_ReturnsSpiffeIdWithTrustDomain() {
val trustDomain = TrustDomain.of("trust-domain.org");
val spiffeId = SpiffeId.of(trustDomain);
assertAll("spiffeId",
() -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()),
() -> assertEquals("", spiffeId.getPath())
);
}
@Test
void of_TrustDomainAndPathsWithCaps_ReturnsSpiffeIdNormalized() {
val trustDomain = TrustDomain.of("TRuST-DoMAIN.Org");
val spiffeId = SpiffeId.of(trustDomain, "PATH1", "paTH2");
assertAll("normalized spiffeId",
() -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()),
() -> assertEquals("/path1/path2", spiffeId.getPath())
);
}
@Test
void of_TrustDomainAndPathWithLeadingAndTrailingBlanks_ReturnsSpiffeIdNormalized() {
val trustDomain = TrustDomain.of(" trust-domain.org ");
val spiffeId = SpiffeId.of(trustDomain, " path1 ", " path2 ");
assertAll("normalized spiffeId",
() -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()),
() -> assertEquals("/path1/path2", spiffeId.getPath())
);
}
@Test
void of_NullTrustDomain_throwsException() {
try {
SpiffeId.of(null);
} catch (NullPointerException e) {
assertEquals("trustDomain is marked non-null but is null", e.getMessage());
}
}
static final Set<Character> PATH_CHARS = Stream.of(
LOWER_ALPHA,
UPPER_ALPHA,
NUMBERS,
SPECIAL_CHARS)
.flatMap(Set::stream)
.collect(Collectors.toSet());
@Test
void toString_SpiffeId_ReturnsTheSpiffeIdInAStringFormatIncludingTheSchema() {
val trustDomain = TrustDomain.of("trust-domain.org");
val spiffeId = SpiffeId.of(trustDomain, "path1", "path2", "path3");
val spiffeIdToString = spiffeId.toString();
assertEquals("spiffe://trust-domain.org/path1/path2/path3", spiffeIdToString);
val trustDomain = TrustDomain.parse("trustdomain");
val spiffeId = SpiffeId.fromSegments(trustDomain, "path1", "path2", "path3");
assertEquals("spiffe://trustdomain/path1/path2/path3", spiffeId.toString());
}
@Test
void memberOf_aTrustDomainAndASpiffeIdWithSameTrustDomain_ReturnTrue() {
val trustDomain = TrustDomain.of("trust-domain.org");
val spiffeId = SpiffeId.of(trustDomain, "path1", "path2");
val isMemberOf = spiffeId.memberOf(TrustDomain.of("trust-domain.org"));
assertTrue(isMemberOf);
@ParameterizedTest
@MethodSource("provideTestValidSpiffeIds")
void testParseValidSpiffeId(String input, TrustDomain expectedTrustDomain, String expectedPath) {
SpiffeId result;
try {
result = SpiffeId.parse(input);
assertEquals(expectedTrustDomain, result.getTrustDomain());
assertEquals(expectedPath, result.getPath());
} catch (Exception e) {
fail("Unexpected error", e);
}
}
@Test
void memberOf_aTrustDomainAndASpiffeIdWithDifferentTrustDomain_ReturnFalse() {
val trustDomain = TrustDomain.of("trust-domain.org");
val spiffeId = SpiffeId.of(trustDomain, "path1", "path2");
val isMemberOf = spiffeId.memberOf(TrustDomain.of("other-domain.org"));
assertFalse(isMemberOf);
}
@Test
void parse_aString_ReturnsASpiffeIdThatHasTrustDomainAndPathSegments() {
val spiffeIdAsString = "spiffe://trust-domain.org/path1/path2";
val spiffeId = SpiffeId.parse(spiffeIdAsString);
assertAll("SpiffeId",
() -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()),
() -> assertEquals("/path1/path2", spiffeId.getPath())
);
}
@Test
void parse_aStringContainingLeadingAndTrailingBlanks_ReturnsASpiffeIdThatHasTrustDomainAndPathSegments() {
val spiffeIdAsString = " spiffe://trust-domain.org/path1/path2 ";
val spiffeId = SpiffeId.parse(spiffeIdAsString);
assertAll("SpiffeId",
() -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()),
() -> assertEquals("/path1/path2", spiffeId.getPath())
);
}
@Test
void parse_pathWithColons() {
val spiffeIdAsString = " spiffe://domain.test/pa:th/element: ";
val spiffeId = SpiffeId.parse(spiffeIdAsString);
assertAll("SpiffeId",
() -> assertEquals("domain.test", spiffeId.getTrustDomain().toString()),
() -> assertEquals("/pa:th/element:", spiffeId.getPath())
);
}
@Test
void parse_pathWithAt() {
val spiffeIdAsString = "spiffe://domain.test/pa@th/element:";
val spiffeId = SpiffeId.parse(spiffeIdAsString);
assertAll("SpiffeId",
() -> assertEquals("domain.test", spiffeId.getTrustDomain().toString()),
() -> assertEquals("/pa@th/element:", spiffeId.getPath())
);
}
@Test
void parse_pathHasEncodedSubdelims() {
val spiffeIdAsString = "spiffe://domain.test/p!a$t&h'/(e)l*e+m,e;n=t";
val spiffeId = SpiffeId.parse(spiffeIdAsString);
assertAll("SpiffeId",
() -> assertEquals("domain.test", spiffeId.getTrustDomain().toString()),
() -> assertEquals("/p!a$t&h'/(e)l*e+m,e;n=t", spiffeId.getPath())
static Stream<Arguments> provideTestValidSpiffeIds() {
return Stream.of(
Arguments.of("spiffe://trustdomain/path", TrustDomain.parse("trustdomain"), "/path"),
Arguments.of("spiffe://trustdomain/path1/path2", TrustDomain.parse("trustdomain"), "/path1/path2"),
Arguments.of("spiffe://trustdomain/PATH1/PATH2", TrustDomain.parse("trustdomain"), "/PATH1/PATH2"),
Arguments.of("spiffe://trustdomain/9eebccd2-12bf-40a6-b262-65fe0487d453", TrustDomain.parse("trustdomain"), "/9eebccd2-12bf-40a6-b262-65fe0487d453")
);
}
@ParameterizedTest
@MethodSource("provideTestInvalidSpiffeIds")
void testParseTrustDomain(String input, Object expected) {
SpiffeId result;
@MethodSource("provideInvalidSpiffeIds")
void testParseInvalidSpiffeId(String input, String expected) {
try {
result = SpiffeId.parse(input);
assertEquals(expected, result.toString());
SpiffeId.parse(input);
fail("Expected validation SPIFFE ID error");
} catch (Exception e) {
assertEquals(expected, e.getMessage());
}
}
static Stream<Arguments> provideTestInvalidSpiffeIds() {
static Stream<Arguments> provideInvalidSpiffeIds() {
return Stream.of(
Arguments.of("", "SPIFFE ID cannot be empty"),
Arguments.of(null, "SPIFFE ID cannot be empty"),
Arguments.of("192.168.2.2:6688", "Illegal character in scheme name at index 0: 192.168.2.2:6688"),
Arguments.of("http://domain.test/path/element", "SPIFFE ID: invalid scheme"),
Arguments.of("spiffe:///path/element", "SPIFFE ID: trust domain is empty"),
Arguments.of("spiffe://domain.test/path/element?query=1", "SPIFFE ID: query is not allowed"),
Arguments.of("spiffe://domain.test/path/element?#fragment-1", "SPIFFE ID: fragment is not allowed"),
Arguments.of("spiffe://domain.test:8080/path/element", "SPIFFE ID: port is not allowed"),
Arguments.of("spiffe://user:password@test.org/path/element", "SPIFFE ID: user info is not allowed"),
Arguments.of("spiffe:path/element", "SPIFFE ID: trust domain is empty"),
Arguments.of("spiffe:/path/element", "SPIFFE ID: trust domain is empty"),
Arguments.of("spiffe://domain.test/path/elem%5uent", "Malformed escape pair at index 30: spiffe://domain.test/path/elem%5uent")
Arguments.of("", "Cannot be empty"),
Arguments.of(null, "Cannot be empty"),
Arguments.of("192.168.2.2:6688", "Scheme is missing or invalid"),
Arguments.of("http://domain.test/path/element", "Scheme is missing or invalid"),
Arguments.of("spiffe:///path/element", "Trust domain is missing"),
Arguments.of("spiffe://domain.test/path/element?query=1", "Path segment characters are limited to letters, numbers, dots, dashes, and underscores"),
Arguments.of("spiffe://domain.test/path/element?#fragment-1", "Path segment characters are limited to letters, numbers, dots, dashes, and underscores"),
Arguments.of("spiffe://domain.test:8080/path/element", "Trust domain characters are limited to lowercase letters, numbers, dots, dashes, and underscores"),
Arguments.of("spiffe://user:password@test.org/path/element", "Trust domain characters are limited to lowercase letters, numbers, dots, dashes, and underscores"),
Arguments.of("spiffe:path/element", "Scheme is missing or invalid"),
Arguments.of("spiffe:/path/element", "Scheme is missing or invalid"),
Arguments.of("SPIFFE://path/element", "Scheme is missing or invalid"),
Arguments.of("spiffe://domain.test/path/elem%5uent", "Path segment characters are limited to letters, numbers, dots, dashes, and underscores"),
Arguments.of("spiffe://trustdomain/path//", "Path cannot contain empty segments"),
Arguments.of("spiffe://trustdomain/./other", "Path cannot contain dot segments"),
Arguments.of("spiffe://trustdomain/../other", "Path cannot contain dot segments"),
Arguments.of("spiffe://trustdomain/", "Path cannot have a trailing slash"),
Arguments.of("spiffe://trustdomain/path/", "Path cannot have a trailing slash")
);
}
@ParameterizedTest
@MethodSource("provideValidTrustDomainAndPaths")
void testOf(TrustDomain inputTrustDomain, String[] inputPath, SpiffeId expectedSpiffeId) {
try {
SpiffeId result = SpiffeId.fromSegments(inputTrustDomain, inputPath);
assertEquals(expectedSpiffeId, result);
} catch (Exception e) {
fail("Unexpected error", e);
}
}
static Stream<Arguments> provideValidTrustDomainAndPaths() {
return Stream.of(
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"path"}, SpiffeId.parse("spiffe://trustdomain/path")),
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"path1", "path2"}, SpiffeId.parse("spiffe://trustdomain/path1/path2")),
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"PATH1", "PATH2"}, SpiffeId.parse("spiffe://trustdomain/PATH1/PATH2")),
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"path1", "path2"}, SpiffeId.parse("spiffe://trustdomain/path1/path2")),
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"9eebccd2-12bf-40a6-b262-65fe0487d453"}, SpiffeId.parse("spiffe://trustdomain/9eebccd2-12bf-40a6-b262-65fe0487d453"))
);
}
@ParameterizedTest
@MethodSource("provideInvalidArguments")
void testOfInvalid(TrustDomain trustDomain, String[] inputPath, String expectedError) {
try {
SpiffeId.fromSegments(trustDomain, inputPath);
fail(String.format("Expected error %s", expectedError));
} catch (Exception e) {
assertEquals(expectedError, e.getMessage());
}
}
static Stream<Arguments> provideInvalidArguments() {
return Stream.of(
Arguments.of(null, new String[]{""}, "trustDomain is marked non-null but is null"),
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"/ele%5ment"}, "Path segment characters are limited to letters, numbers, dots, dashes, and underscores"),
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"/path/"}, "Path cannot have a trailing slash"),
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"/ /"}, "Path segment characters are limited to letters, numbers, dots, dashes, and underscores"),
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"/"}, "Path cannot have a trailing slash"),
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"//"}, "Path cannot contain empty segments"),
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"/./"}, "Path cannot contain dot segments"),
Arguments.of(TrustDomain.parse("trustdomain"), new String[]{"/../"}, "Path cannot contain dot segments")
);
}
@Test
void testParseWithAllChars() {
// Go all the way through 255, which ensures we reject UTF-8 appropriately
for (int i = 0; i < 256; i++) {
char c = (char) i;
// Don't test '/' since it is the delimiter between path segments
if (c == '/') {
continue;
}
String path = "/path" + c;
if (PATH_CHARS.contains(c)) {
SpiffeId spiffeId = SpiffeId.parse("spiffe://trustdomain" + path);
assertEquals(spiffeId.toString(), "spiffe://trustdomain" + path);
} else {
try {
SpiffeId.parse("spiffe://trustdomain" + path);
} catch (InvalidSpiffeIdException e) {
assertEquals("Path segment characters are limited to letters, numbers, dots, dashes, and underscores", e.getMessage());
}
}
String td = "spiffe://trustdomain" + c;
if (TD_CHARS.contains(c)) {
SpiffeId spiffeId = SpiffeId.parse(td);
assertEquals(spiffeId.toString(), td);
} else {
try {
SpiffeId.parse(td);
} catch (InvalidSpiffeIdException e) {
assertEquals("Trust domain characters are limited to lowercase letters, numbers, dots, dashes, and underscores", e.getMessage());
}
}
}
}
@Test
void testOfWithAllChars() {
// Go all the way through 255, which ensures we reject UTF-8 appropriately
for (int i = 0; i < 256; i++) {
char c = (char) i;
// Don't test '/' since it is the delimiter between path segments
if (c == '/') {
continue;
}
String path1 = "path1" + c;
String path2 = "path2" + c;
TrustDomain td = TrustDomain.parse("trustdomain");
if (PATH_CHARS.contains(c)) {
SpiffeId spiffeId = SpiffeId.fromSegments(td, path1, path2);
assertEquals(spiffeId.toString(), String.format("spiffe://trustdomain/%s/%s", path1, path2));
} else {
try {
SpiffeId.fromSegments(td, path1, path2);
} catch (InvalidSpiffeIdException e) {
assertEquals("Path segment characters are limited to letters, numbers, dots, dashes, and underscores", e.getMessage());
}
}
}
}
@Test
void memberOf_aTrustDomainAndASpiffeIdWithSameTrustDomain_ReturnsTrue() {
val trustDomain = TrustDomain.parse("trustdomain");
val spiffeId = SpiffeId.fromSegments(trustDomain, "path1", "path2");
val isMemberOf = spiffeId.memberOf(TrustDomain.parse("trustdomain"));
assertTrue(isMemberOf);
}
@Test
void memberOf_aTrustDomainAndASpiffeIdWithDifferentTrustDomain_ReturnsFalse() {
val trustDomain = TrustDomain.parse("trustdomain");
val spiffeId = SpiffeId.fromSegments(trustDomain, "path1", "path2");
val isMemberOf = spiffeId.memberOf(TrustDomain.parse("otherdomain"));
assertFalse(isMemberOf);
}
}

View File

@ -50,7 +50,7 @@ class SpiffeIdUtilsTest {
@Test
void toSetOfSpiffeIdsDefaultSeparator() {
val spiffeIdsAsString = " spiffe://example.org/workload1 | spiffe://example.org/workload2 ";
val spiffeIdsAsString = "spiffe://example.org/workload1|spiffe://example.org/workload2";
val spiffeIdSet = SpiffeIdUtils.toSetOfSpiffeIds(spiffeIdsAsString);
assertNotNull(spiffeIdSet);
@ -61,7 +61,7 @@ class SpiffeIdUtilsTest {
@Test
void toSetOfSpiffeIdsBlankSpaceSeparator() {
val spiffeIdsAsString = " spiffe://example.org/workload1 spiffe://example.org/workload2 ";
val spiffeIdsAsString = "spiffe://example.org/workload1 spiffe://example.org/workload2";
val spiffeIdSet = SpiffeIdUtils.toSetOfSpiffeIds(spiffeIdsAsString, ' ');
assertNotNull(spiffeIdSet);
@ -71,14 +71,14 @@ class SpiffeIdUtilsTest {
}
@Test
void toSetOfSpiffeIdsInvalidSeparator() {
val spiffeIdsAsString = " spiffe://example.org/workload1, spiffe://example.org/workload2 ";
try {
SpiffeIdUtils.toSetOfSpiffeIds(spiffeIdsAsString, ',');
fail();
} catch (IllegalArgumentException e) {
assertEquals("Separator character is not supported.", e.getMessage());
}
void toSetOfSpiffeIdsCommaSeparator() {
val spiffeIdsAsString = "spiffe://example.org/workload1,spiffe://example.org/workload2";
val spiffeIdSet = SpiffeIdUtils.toSetOfSpiffeIds(spiffeIdsAsString, ',');
assertNotNull(spiffeIdSet);
assertEquals(2, spiffeIdSet.size());
assertTrue(spiffeIdSet.contains(SpiffeId.parse("spiffe://example.org/workload1")));
assertTrue(spiffeIdSet.contains(SpiffeId.parse("spiffe://example.org/workload2")));
}
@Test

View File

@ -1,5 +1,7 @@
package io.spiffe.spiffeid;
import io.spiffe.exception.InvalidSpiffeIdException;
import lombok.val;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@ -7,25 +9,85 @@ import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static io.spiffe.spiffeid.SpiffeIdTest.TD_CHARS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class TrustDomainTest {
class TrustDomainTest {
@Test
void testTrustDomainFromName() {
TrustDomain trustDomain = TrustDomain.parse("trustdomain");
assertEquals("trustdomain", trustDomain.getName());
}
@Test
void testFromIdStringWithoutPath() {
TrustDomain trustDomain = TrustDomain.parse("spiffe://trustdomain");
assertEquals("trustdomain", trustDomain.getName());
}
@Test
void testFromIdStringWithPath() {
TrustDomain trustDomain = TrustDomain.parse("spiffe://trustdomain/path");
assertEquals("trustdomain", trustDomain.getName());
}
@Test
void testAllChars() {
// Go all the way through 255, which ensures we reject UTF-8 appropriately
for (int i = 0; i < 256; i++) {
char c = (char) i;
String td = "trustdomain" + c;
if (TD_CHARS.contains(c)) {
TrustDomain trustDomain = TrustDomain.parse(td);
assertEquals(td, trustDomain.getName());
} else {
try {
TrustDomain.parse(td);
} catch (InvalidSpiffeIdException e) {
assertEquals(e.getMessage(), "Trust domain characters are limited to lowercase letters, numbers, dots, dashes, and underscores");
}
}
}
}
@ParameterizedTest
@MethodSource("provideTestTrustDomain")
@MethodSource("provideInvalidTrustDomain")
void testParseTrustDomain(String input, Object expected) {
TrustDomain result;
try {
result = TrustDomain.of(input);
assertEquals(expected, result.getName());
TrustDomain.parse(input);
fail("error expected");
} catch (Exception e) {
assertEquals(expected, e.getMessage());
assertEquals(expected, e.getMessage().trim());
}
}
static Stream<Arguments> provideInvalidTrustDomain() {
return Stream.of(
Arguments.of("", "Trust domain is missing"),
Arguments.of("spiffe://", "Trust domain is missing"),
Arguments.of(null, "idOrName is marked non-null but is null"),
Arguments.of("Trustdomain", "Trust domain characters are limited to lowercase letters, numbers, dots, dashes, and underscores"),
Arguments.of("spiffe://Domain.test", "Trust domain characters are limited to lowercase letters, numbers, dots, dashes, and underscores"),
Arguments.of("spiffe://domain.test/spiffe://domain.test/path/element", "Path segment characters are limited to letters, numbers, dots, dashes, and underscores"),
Arguments.of("http://domain.test", "Scheme is missing or invalid"),
Arguments.of("spiffe:// domain.test ", "Trust domain characters are limited to lowercase letters, numbers, dots, dashes, and underscores"),
Arguments.of("://domain.test", "Scheme is missing or invalid"),
Arguments.of("spiffe:///path/element", "Trust domain is missing"),
Arguments.of("/path/element", "Trust domain characters are limited to lowercase letters, numbers, dots, dashes, and underscores"),
Arguments.of("spiffe://domain.test:80", "Trust domain characters are limited to lowercase letters, numbers, dots, dashes, and underscores"),
Arguments.of("spiffe:/trustdomain/path", "Scheme is missing or invalid"),
Arguments.of("spiffe://trustdomain/", "Path cannot have a trailing slash"),
Arguments.of("spiffe://trustdomain/path/", "Path cannot have a trailing slash")
);
}
@Test
void testNewSpiffeId() {
TrustDomain trustDomain = TrustDomain.of("test.domain");
TrustDomain trustDomain = TrustDomain.parse("test.domain");
SpiffeId spiffeId = trustDomain.newSpiffeId("path1", "host");
assertEquals(trustDomain, spiffeId.getTrustDomain());
@ -34,31 +96,13 @@ public class TrustDomainTest {
@Test
void testToString() {
TrustDomain trustDomain = TrustDomain.of("test.domain");
TrustDomain trustDomain = TrustDomain.parse("test.domain");
assertEquals("test.domain", trustDomain.toString());
}
@Test
void testGetName() {
TrustDomain trustDomain = TrustDomain.of("test.domain");
assertEquals("test.domain", trustDomain.getName());
}
static Stream<Arguments> provideTestTrustDomain() {
return Stream.of(
Arguments.of("", "Trust domain cannot be empty"),
Arguments.of(null, "trustDomain is marked non-null but is null"),
Arguments.of(" DomAin.TesT ", "domain.test"),
Arguments.of(" spiffe://domaiN.Test ", "domain.test"),
Arguments.of("spiffe://domain.test/path/element", "domain.test"),
Arguments.of("spiffe://domain.test/spiffe://domain.test/path/element", "domain.test"),
Arguments.of("spiffe://domain.test/spiffe://domain.test:80/path/element", "domain.test"),
Arguments.of("http://domain.test", "Invalid scheme"),
Arguments.of("spiffe:// domain.test ", "Illegal character in authority at index 9: spiffe:// domain.test"),
Arguments.of("://domain.test", "Expected scheme name at index 0: ://domain.test"),
Arguments.of("spiffe:///path/element", "Trust domain cannot be empty"),
Arguments.of("/path/element", "Trust domain cannot be empty"),
Arguments.of("spiffe://domain.test:80", "Trust Domain: port is not allowed")
);
void test_toIdString() {
val trustDomain = TrustDomain.parse("domain.test");
assertEquals("spiffe://domain.test", trustDomain.toIdString());
}
}

View File

@ -1,79 +0,0 @@
package io.spiffe.svid.jwtsvid;
import io.spiffe.Algorithm;
import lombok.Builder;
import lombok.Value;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AlgorithmTest {
@ParameterizedTest
@MethodSource("provideTestCases")
void parse(TestCase testCase) {
Algorithm signatureAlgorithm = Algorithm.parse(testCase.name);
assertEquals(testCase.expectedAlgorithm, signatureAlgorithm);
assertEquals(testCase.name, signatureAlgorithm.getName());
}
static Stream<Arguments> provideTestCases() {
return Stream.of(
Arguments.of(TestCase.builder().name("RS256").expectedAlgorithm(Algorithm.RS256).build()),
Arguments.of(TestCase.builder().name("RS384").expectedAlgorithm(Algorithm.RS384).build()),
Arguments.of(TestCase.builder().name("RS512").expectedAlgorithm(Algorithm.RS512).build()),
Arguments.of(TestCase.builder().name("ES256").expectedAlgorithm(Algorithm.ES256).build()),
Arguments.of(TestCase.builder().name("ES384").expectedAlgorithm(Algorithm.ES384).build()),
Arguments.of(TestCase.builder().name("ES512").expectedAlgorithm(Algorithm.ES512).build()),
Arguments.of(TestCase.builder().name("PS256").expectedAlgorithm(Algorithm.PS256).build()),
Arguments.of(TestCase.builder().name("PS384").expectedAlgorithm(Algorithm.PS384).build()),
Arguments.of(TestCase.builder().name("PS512").expectedAlgorithm(Algorithm.PS512).build()),
Arguments.of(TestCase.builder().name("OTHER").expectedAlgorithm(Algorithm.OTHER).build())
);
}
@Value
static class TestCase {
String name;
Algorithm expectedAlgorithm;
@Builder
public TestCase(String name, Algorithm expectedAlgorithm) {
this.name = name;
this.expectedAlgorithm = expectedAlgorithm;
}
}
@Test
void testParseFamilyRSA() {
Algorithm.Family rsa = Algorithm.Family.parse("RSA");
assertEquals(Algorithm.Family.RSA, rsa);
assertTrue(rsa.contains(Algorithm.RS256));
assertTrue(rsa.contains(Algorithm.RS384));
assertTrue(rsa.contains(Algorithm.RS512));
assertTrue(rsa.contains(Algorithm.PS256));
assertTrue(rsa.contains(Algorithm.PS384));
assertTrue(rsa.contains(Algorithm.PS512));
}
@Test
void testParseFamilyEC() {
Algorithm.Family ec = Algorithm.Family.parse("EC");
assertEquals(Algorithm.Family.EC, ec);
assertTrue(ec.contains(Algorithm.ES256));
assertTrue(ec.contains(Algorithm.ES384));
assertTrue(ec.contains(Algorithm.ES512));
}
@Test
void testParseFamilyOTHER() {
Algorithm.Family other = Algorithm.Family.parse("unknown family");
assertEquals(Algorithm.Family.OTHER, other);
}
}

View File

@ -0,0 +1,93 @@
package io.spiffe.svid.jwtsvid;
import io.spiffe.internal.JwtSignatureAlgorithm;
import lombok.Builder;
import lombok.Value;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
class JwtSignatureAlgorithmTest {
@ParameterizedTest
@MethodSource("provideTestCases")
void parse(TestCase testCase) {
JwtSignatureAlgorithm signatureAlgorithm = JwtSignatureAlgorithm.parse(testCase.name);
assertEquals(testCase.expectedAlgorithm, signatureAlgorithm);
assertEquals(testCase.name, signatureAlgorithm.getName());
}
static Stream<Arguments> provideTestCases() {
return Stream.of(
Arguments.of(TestCase.builder().name("RS256").expectedAlgorithm(JwtSignatureAlgorithm.RS256).build()),
Arguments.of(TestCase.builder().name("RS384").expectedAlgorithm(JwtSignatureAlgorithm.RS384).build()),
Arguments.of(TestCase.builder().name("RS512").expectedAlgorithm(JwtSignatureAlgorithm.RS512).build()),
Arguments.of(TestCase.builder().name("ES256").expectedAlgorithm(JwtSignatureAlgorithm.ES256).build()),
Arguments.of(TestCase.builder().name("ES384").expectedAlgorithm(JwtSignatureAlgorithm.ES384).build()),
Arguments.of(TestCase.builder().name("ES512").expectedAlgorithm(JwtSignatureAlgorithm.ES512).build()),
Arguments.of(TestCase.builder().name("PS256").expectedAlgorithm(JwtSignatureAlgorithm.PS256).build()),
Arguments.of(TestCase.builder().name("PS384").expectedAlgorithm(JwtSignatureAlgorithm.PS384).build()),
Arguments.of(TestCase.builder().name("PS512").expectedAlgorithm(JwtSignatureAlgorithm.PS512).build())
);
}
@Value
static class TestCase {
String name;
JwtSignatureAlgorithm expectedAlgorithm;
@Builder
public TestCase(String name, JwtSignatureAlgorithm expectedAlgorithm) {
this.name = name;
this.expectedAlgorithm = expectedAlgorithm;
}
}
@Test
void testParseFamilyRSA() {
JwtSignatureAlgorithm.Family rsa = JwtSignatureAlgorithm.Family.parse("RSA");
assertEquals(JwtSignatureAlgorithm.Family.RSA, rsa);
assertTrue(rsa.contains(JwtSignatureAlgorithm.RS256));
assertTrue(rsa.contains(JwtSignatureAlgorithm.RS384));
assertTrue(rsa.contains(JwtSignatureAlgorithm.RS512));
assertTrue(rsa.contains(JwtSignatureAlgorithm.PS256));
assertTrue(rsa.contains(JwtSignatureAlgorithm.PS384));
assertTrue(rsa.contains(JwtSignatureAlgorithm.PS512));
}
@Test
void testParseFamilyEC() {
JwtSignatureAlgorithm.Family ec = JwtSignatureAlgorithm.Family.parse("EC");
assertEquals(JwtSignatureAlgorithm.Family.EC, ec);
assertTrue(ec.contains(JwtSignatureAlgorithm.ES256));
assertTrue(ec.contains(JwtSignatureAlgorithm.ES384));
assertTrue(ec.contains(JwtSignatureAlgorithm.ES512));
}
@Test
void testParseUnknownFamily() {
try {
JwtSignatureAlgorithm.Family.parse("unknown family");
fail();
} catch (IllegalArgumentException e) {
assertEquals("Unsupported JWT family algorithm: unknown family", e.getMessage());
}
}
@Test
void testParseUnsupportedAlgorithm() {
try {
JwtSignatureAlgorithm.parse("HS256");
fail();
} catch (IllegalArgumentException e) {
assertEquals("Unsupported JWT algorithm: HS256", e.getMessage());
}
}
}

View File

@ -26,6 +26,7 @@ import java.util.stream.Stream;
import static io.spiffe.svid.jwtsvid.JwtSvidParseInsecureTest.newJwtSvidInstance;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
class JwtSvidParseAndValidateTest {
@ -34,18 +35,30 @@ class JwtSvidParseAndValidateTest {
"dCI6MTUxNjIzOTAyMiwiYXVkIjoiYXVkaWVuY2UifQ.wNm5pQGSLCw5N9ddgSF2hkgmQpGnG9le_gpiFmyBhao";
@ParameterizedTest
@MethodSource("provideJwtScenarios")
void parseAndValidateJwt(TestCase testCase) {
@MethodSource("provideSuccessScenarios")
void parseAndValidateValidJwt(TestCase testCase) {
try {
String token = testCase.generateToken.get();
JwtSvid jwtSvid = JwtSvid.parseAndValidate(token, testCase.jwtBundle, testCase.audience);
JwtSvid jwtSvid = JwtSvid.parseAndValidate(token, testCase.jwtBundle, testCase.audience, testCase.hint);
assertEquals(testCase.expectedJwtSvid.getSpiffeId(), jwtSvid.getSpiffeId());
assertEquals(testCase.expectedJwtSvid.getAudience(), jwtSvid.getAudience());
assertEquals(testCase.expectedJwtSvid.getHint(), jwtSvid.getHint());
assertEquals(testCase.expectedJwtSvid.getExpiry().toInstant().getEpochSecond(), jwtSvid.getExpiry().toInstant().getEpochSecond());
assertEquals(token, jwtSvid.getToken());
assertEquals(token, jwtSvid.marshal());
} catch (Exception e) {
fail(e);
}
}
@ParameterizedTest
@MethodSource("provideFailureScenarios")
void parseAndValidateInvalidJwt(TestCase testCase) {
try {
String token = testCase.generateToken.get();
JwtSvid.parseAndValidate(token, testCase.jwtBundle, testCase.audience);
fail("expected error: " + testCase.expectedException.getMessage());
} catch (Exception e) {
assertEquals(testCase.expectedException.getClass(), e.getClass());
assertEquals(testCase.expectedException.getMessage(), e.getMessage());
@ -54,7 +67,7 @@ class JwtSvidParseAndValidateTest {
@Test
void testParseAndValidate_nullToken_throwsNullPointerException() throws JwtSvidException, AuthorityNotFoundException, BundleNotFoundException {
TrustDomain trustDomain = TrustDomain.of("test.domain");
TrustDomain trustDomain = TrustDomain.parse("test.domain");
JwtBundle jwtBundle = new JwtBundle(trustDomain);
Set<String> audience = Collections.singleton("audience");
@ -67,7 +80,7 @@ class JwtSvidParseAndValidateTest {
@Test
void testParseAndValidate_emptyToken_throwsIllegalArgumentException() throws JwtSvidException, AuthorityNotFoundException, BundleNotFoundException {
TrustDomain trustDomain = TrustDomain.of("test.domain");
TrustDomain trustDomain = TrustDomain.parse("test.domain");
JwtBundle jwtBundle = new JwtBundle(trustDomain);
Set<String> audience = Collections.singleton("audience");
@ -90,7 +103,7 @@ class JwtSvidParseAndValidateTest {
@Test
void testParseAndValidate_nullAudience_throwsNullPointerException() throws JwtSvidException, AuthorityNotFoundException, BundleNotFoundException {
TrustDomain trustDomain = TrustDomain.of("test.domain");
TrustDomain trustDomain = TrustDomain.parse("test.domain");
JwtBundle jwtBundle = new JwtBundle(trustDomain);
try {
@ -100,138 +113,193 @@ class JwtSvidParseAndValidateTest {
}
}
static Stream<Arguments> provideJwtScenarios() {
static Stream<Arguments> provideSuccessScenarios() {
KeyPair key1 = TestUtils.generateECKeyPair(Curve.P_521);
KeyPair key2 = TestUtils.generateECKeyPair(Curve.P_521);
KeyPair key3 = TestUtils.generateRSAKeyPair(2048);
TrustDomain trustDomain = TrustDomain.of("test.domain");
TrustDomain trustDomain = TrustDomain.parse("test.domain");
JwtBundle jwtBundle = new JwtBundle(trustDomain);
jwtBundle.putJwtAuthority("authority1", key1.getPublic());
jwtBundle.putJwtAuthority("authority2", key2.getPublic());
jwtBundle.putJwtAuthority("authority3", key3.getPublic());
SpiffeId spiffeId = trustDomain.newSpiffeId("host");
Date expiration = new Date(System.currentTimeMillis() + 3600000);
Date issuedAt = new Date();
Date expiration = new Date(System.currentTimeMillis() + (60 * 60 * 1000));
Set<String> audience = new HashSet<String>() {{add("audience1"); add("audience2");}};
JWTClaimsSet claims = TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), expiration);
return Stream.of(
Arguments.of(TestCase.builder()
.name("1. success using EC signature")
.name("using EC signature")
.jwtBundle(jwtBundle)
.expectedAudience(Collections.singleton("audience1"))
.generateToken(() -> TestUtils.generateToken(claims, key1, "authority1"))
.generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JOSE))
.expectedException(null)
.hint("external")
.expectedJwtSvid(newJwtSvidInstance(
trustDomain.newSpiffeId("host"),
audience,
issuedAt,
expiration,
claims.getClaims(), TestUtils.generateToken(claims, key1, "authority1") ))
claims.getClaims(),
TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JOSE),
"external"
))
.build()),
Arguments.of(TestCase.builder()
.name("2. success using RSA signature")
.name("using RSA signature")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key3, "authority3"))
.generateToken(() -> TestUtils.generateToken(claims, key3, "authority3", JwtSvid.HEADER_TYP_JWT))
.expectedException(null)
.hint("internal")
.expectedJwtSvid(newJwtSvidInstance(
trustDomain.newSpiffeId("host"),
audience,
issuedAt,
expiration,
claims.getClaims(), TestUtils.generateToken(claims, key3, "authority3")))
claims.getClaims(), TestUtils.generateToken(claims, key3, "authority3", JwtSvid.HEADER_TYP_JWT),
"internal"
))
.build()),
Arguments.of(TestCase.builder()
.name("3. malformed")
.name("using empty typ")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key3, "authority3", ""))
.expectedException(null)
.hint("")
.expectedJwtSvid(newJwtSvidInstance(
trustDomain.newSpiffeId("host"),
audience,
issuedAt,
expiration,
claims.getClaims(),
TestUtils.generateToken(claims, key3, "authority3"),
""
))
.build())
);
}
static Stream<Arguments> provideFailureScenarios() {
KeyPair key1 = TestUtils.generateECKeyPair(Curve.P_521);
KeyPair key2 = TestUtils.generateECKeyPair(Curve.P_521);
KeyPair key3 = TestUtils.generateRSAKeyPair(2048);
TrustDomain trustDomain = TrustDomain.parse("test.domain");
JwtBundle jwtBundle = new JwtBundle(trustDomain);
jwtBundle.putJwtAuthority("authority1", key1.getPublic());
jwtBundle.putJwtAuthority("authority2", key2.getPublic());
jwtBundle.putJwtAuthority("authority3", key3.getPublic());
SpiffeId spiffeId = trustDomain.newSpiffeId("host");
Date expiration = new Date(System.currentTimeMillis() + (60 * 60 * 1000));
Set<String> audience = new HashSet<String>() {{add("audience1"); add("audience2");}};
JWTClaimsSet claims = TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), expiration);
return Stream.of(
Arguments.of(TestCase.builder()
.name("malformed")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> "invalid token")
.expectedException(new IllegalArgumentException("Unable to parse JWT token"))
.build()),
Arguments.of(TestCase.builder()
.name("4. unsupported algorithm")
.name("unsupported algorithm")
.jwtBundle(jwtBundle)
.expectedAudience(Collections.singleton("audience"))
.generateToken(() -> HS256TOKEN)
.expectedException(new JwtSvidException("Unsupported token signature algorithm HS256"))
.expectedException(new JwtSvidException("Unsupported JWT algorithm: HS256"))
.build()),
Arguments.of(TestCase.builder()
.name("5. missing subject")
.name("missing subject")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(TestUtils.buildJWTClaimSet(audience, "", expiration), key1, "authority1"))
.expectedException(new JwtSvidException("Token missing subject claim"))
.build()),
Arguments.of(TestCase.builder()
.name("6. missing expiration")
.name("missing expiration")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), null), key1, "authority1"))
.expectedException(new JwtSvidException("Token missing expiration claim"))
.build()),
Arguments.of(TestCase.builder()
.name("7. token has expired")
.name("token has expired")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), new Date()), key1, "authority1"))
.expectedException(new JwtSvidException("Token has expired"))
.build()),
Arguments.of(TestCase.builder()
.name("8. unexpected audience")
.name("unexpected audience")
.jwtBundle(jwtBundle)
.expectedAudience(Collections.singleton("another"))
.generateToken(() -> TestUtils.generateToken(claims, key1, "authority1"))
.expectedException(new JwtSvidException("expected audience in [another] (audience=[audience2, audience1])"))
.build()),
Arguments.of(TestCase.builder()
.name("9. invalid subject claim")
.name("invalid subject claim")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(TestUtils.buildJWTClaimSet(audience, "non-spiffe-subject", expiration), key1, "authority1"))
.expectedException(new JwtSvidException("Subject non-spiffe-subject cannot be parsed as a SPIFFE ID"))
.build()),
Arguments.of(TestCase.builder()
.name("10. missing key id")
.name("missing key id")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key1, null))
.expectedException(new JwtSvidException("Token header missing key id"))
.build()),
Arguments.of(TestCase.builder()
.name("11. key id contains an empty value")
.name("key id contains an empty value")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key1, " "))
.expectedException(new JwtSvidException("Token header key id contains an empty value"))
.build()),
Arguments.of(TestCase.builder()
.name("12. no bundle for trust domain")
.jwtBundle(new JwtBundle(TrustDomain.of("other.domain")))
.name("no bundle for trust domain")
.jwtBundle(new JwtBundle(TrustDomain.parse("other.domain")))
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key1, "authority1"))
.expectedException(new BundleNotFoundException("No JWT bundle found for trust domain test.domain"))
.build()),
Arguments.of(TestCase.builder()
.name("13. no authority found for key id")
.jwtBundle(new JwtBundle(TrustDomain.of("test.domain")))
.name("no authority found for key id")
.jwtBundle(new JwtBundle(TrustDomain.parse("test.domain")))
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key1, "authority1"))
.expectedException(new AuthorityNotFoundException("No authority found for the trust domain test.domain and key id authority1"))
.build()),
Arguments.of(TestCase.builder()
.name("14. signature cannot be verified with authority")
.name("signature cannot be verified with authority")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key2, "authority1"))
.expectedException(new JwtSvidException("Signature invalid: cannot be verified with the authority with keyId=authority1"))
.build()),
Arguments.of(TestCase.builder()
.name("15. authority algorithm mismatch")
.name("authority algorithm mismatch")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key3, "authority1"))
.expectedException(new JwtSvidException("Error verifying signature with the authority with keyId=authority1"))
.build()),
Arguments.of(TestCase.builder()
.name("not valid header 'typ'")
.jwtBundle(jwtBundle)
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", "OTHER"))
.expectedException(new JwtSvidException("If JWT header 'typ' is present, it must be either 'JWT' or 'JOSE'. Got: 'OTHER'."))
.build())
);
}
@ -241,19 +309,21 @@ class JwtSvidParseAndValidateTest {
String name;
JwtBundle jwtBundle;
Set<String> audience;
String hint;
Supplier<String> generateToken;
Exception expectedException;
JwtSvid expectedJwtSvid;
@Builder
public TestCase(String name, JwtBundle jwtBundle, Set<String> expectedAudience, Supplier<String> generateToken,
Exception expectedException, JwtSvid expectedJwtSvid) {
Exception expectedException, JwtSvid expectedJwtSvid, String hint) {
this.name = name;
this.jwtBundle = jwtBundle;
this.audience = expectedAudience;
this.generateToken = generateToken;
this.expectedException = expectedException;
this.expectedJwtSvid = expectedJwtSvid;
this.hint = hint;
}
}
}

View File

@ -25,21 +25,38 @@ import java.util.function.Supplier;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
class JwtSvidParseInsecureTest {
@ParameterizedTest
@MethodSource("provideJwtScenarios")
void parseJwt(TestCase testCase) {
private static final String HS256TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImF1dGhvcml0eTEifQ." +
"eyJzdWIiOiJzcGlmZmU6Ly90ZXN0LmRvbWFpbi9ob3N0IiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxMjM0MzQzNTM0NTUsImlh" +
"dCI6MTUxNjIzOTAyMiwiYXVkIjoiYXVkaWVuY2UifQ.wNm5pQGSLCw5N9ddgSF2hkgmQpGnG9le_gpiFmyBhao";
@ParameterizedTest
@MethodSource("provideSuccessScenarios")
void parseValidJwt(TestCase testCase) {
try {
String token = testCase.generateToken.get();
JwtSvid jwtSvid = JwtSvid.parseInsecure(token, testCase.audience);
JwtSvid jwtSvid = JwtSvid.parseInsecure(token, testCase.audience, testCase.hint);
assertEquals(testCase.expectedJwtSvid.getSpiffeId(), jwtSvid.getSpiffeId());
assertEquals(testCase.expectedJwtSvid.getAudience(), jwtSvid.getAudience());
assertEquals(testCase.expectedJwtSvid.getHint(), jwtSvid.getHint());
assertEquals(testCase.expectedJwtSvid.getExpiry().toInstant().getEpochSecond(), jwtSvid.getExpiry().toInstant().getEpochSecond());
assertEquals(token, jwtSvid.getToken());
} catch (Exception e) {
fail(e);
}
}
@ParameterizedTest
@MethodSource("provideFailureScenarios")
void parseInvalidJwt(TestCase testCase) {
try {
String token = testCase.generateToken.get();
JwtSvid.parseInsecure(token, testCase.audience);
fail("expected error: " + testCase.expectedException.getMessage());
} catch (Exception e) {
assertEquals(testCase.expectedException.getClass(), e.getClass());
assertEquals(testCase.expectedException.getMessage(), e.getMessage());
@ -72,8 +89,8 @@ class JwtSvidParseInsecureTest {
void testParseInsecure_nullAudience_throwsNullPointerException() throws JwtSvidException {
try {
KeyPair key1 = TestUtils.generateECKeyPair(Curve.P_521);
TrustDomain trustDomain = TrustDomain.of("test.domain");
SpiffeId spiffeId = trustDomain.newSpiffeId("host");
TrustDomain trustDomain = TrustDomain.parse("test.domain");
SpiffeId spiffeId = trustDomain.newSpiffeId("/host");
Set<String> audience = Collections.singleton("audience");
Date expiration = new Date(System.currentTimeMillis() + 3600000);
JWTClaimsSet claims = TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), expiration);
@ -85,11 +102,78 @@ class JwtSvidParseInsecureTest {
}
}
static Stream<Arguments> provideJwtScenarios() {
static Stream<Arguments> provideSuccessScenarios() {
KeyPair key1 = TestUtils.generateECKeyPair(Curve.P_521);
KeyPair key2 = TestUtils.generateECKeyPair(Curve.P_521);
TrustDomain trustDomain = TrustDomain.of("test.domain");
TrustDomain trustDomain = TrustDomain.parse("test.domain");
JwtBundle jwtBundle = new JwtBundle(trustDomain);
jwtBundle.putJwtAuthority("authority1", key1.getPublic());
jwtBundle.putJwtAuthority("authority2", key2.getPublic());
SpiffeId spiffeId = trustDomain.newSpiffeId("host");
Date expiration = new Date(System.currentTimeMillis() + 3600000);
Date issuedAt = new Date();
Set<String> audience = Collections.singleton("audience");
JWTClaimsSet claims = TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), expiration);
return Stream.of(
Arguments.of(TestCase.builder()
.name("using typ as JWT")
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JWT))
.expectedException(null)
.hint("internal")
.expectedJwtSvid(newJwtSvidInstance(
trustDomain.newSpiffeId("host"),
audience,
issuedAt,
expiration,
claims.getClaims(),
TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JWT),
"internal"
))
.build()),
Arguments.of(TestCase.builder()
.name("using typ as JOSE")
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JOSE))
.expectedException(null)
.hint("external")
.expectedJwtSvid(newJwtSvidInstance(
trustDomain.newSpiffeId("host"),
audience,
issuedAt,
expiration,
claims.getClaims(),
TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JWT),
"external"
))
.build()),
Arguments.of(TestCase.builder()
.name("using empty typ")
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", ""))
.expectedException(null)
.hint("")
.expectedJwtSvid(newJwtSvidInstance(
trustDomain.newSpiffeId("host"),
audience,
issuedAt,
expiration,
claims.getClaims(),
TestUtils.generateToken(claims, key1, "authority1", ""),
""
))
.build()));
}
static Stream<Arguments> provideFailureScenarios() {
KeyPair key1 = TestUtils.generateECKeyPair(Curve.P_521);
KeyPair key2 = TestUtils.generateECKeyPair(Curve.P_521);
TrustDomain trustDomain = TrustDomain.parse("test.domain");
JwtBundle jwtBundle = new JwtBundle(trustDomain);
jwtBundle.putJwtAuthority("authority1", key1.getPublic());
jwtBundle.putJwtAuthority("authority2", key2.getPublic());
@ -101,17 +185,6 @@ class JwtSvidParseInsecureTest {
JWTClaimsSet claims = TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), expiration);
return Stream.of(
Arguments.of(TestCase.builder()
.name("success")
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key1, "authority1"))
.expectedException(null)
.expectedJwtSvid(newJwtSvidInstance(
trustDomain.newSpiffeId("host"),
audience,
expiration,
claims.getClaims(), TestUtils.generateToken(claims, key1, "authority1")))
.build()),
Arguments.of(TestCase.builder()
.name("malformed")
.expectedAudience(audience)
@ -147,6 +220,12 @@ class JwtSvidParseInsecureTest {
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(TestUtils.buildJWTClaimSet(audience, "non-spiffe-subject", expiration), key1, "authority1"))
.expectedException(new JwtSvidException("Subject non-spiffe-subject cannot be parsed as a SPIFFE ID"))
.build()),
Arguments.of(TestCase.builder()
.name("not valid header 'typ'")
.expectedAudience(audience)
.generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", "OTHER"))
.expectedException(new JwtSvidException("If JWT header 'typ' is present, it must be either 'JWT' or 'JOSE'. Got: 'OTHER'."))
.build())
);
}
@ -155,30 +234,35 @@ class JwtSvidParseInsecureTest {
static class TestCase {
String name;
Set<String> audience;
String hint;
Supplier<String> generateToken;
Exception expectedException;
JwtSvid expectedJwtSvid;
@Builder
public TestCase(String name, Set<String> expectedAudience, Supplier<String> generateToken,
Exception expectedException, JwtSvid expectedJwtSvid) {
Exception expectedException, JwtSvid expectedJwtSvid, String hint) {
this.name = name;
this.audience = expectedAudience;
this.generateToken = generateToken;
this.expectedException = expectedException;
this.expectedJwtSvid = expectedJwtSvid;
this.hint = hint;
}
}
static JwtSvid newJwtSvidInstance(final SpiffeId spiffeId,
final Set<String> audience,
final Date issuedAt,
final Date expiry,
final Map<String, Object> claims,
final String token) {
final String token,
final String hint
) {
val constructor = JwtSvid.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
try {
return (JwtSvid) constructor.newInstance(spiffeId, audience, expiry, claims, token);
return (JwtSvid) constructor.newInstance(spiffeId, audience, issuedAt, expiry, claims, token, hint);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}

View File

@ -24,10 +24,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
public class X509SvidTest {
class X509SvidTest {
static String keyRSA = "testdata/x509svid/key-pkcs8-rsa.pem";
static String keyRSAOther = "testdata/x509svid/key-rsa-other.pem";
static String certSingle = "testdata/x509svid/good-leaf-only.pem";
static String leafNoDigitalSignature = "testdata/x509svid/wrong-leaf-no-digital-signature.pem";
static String leafCRLSign = "testdata/x509svid/wrong-leaf-crl-sign.pem";
@ -39,7 +38,6 @@ public class X509SvidTest {
static String keyECDSA = "testdata/x509svid/key-pkcs8-ecdsa.pem";
static String certMultiple = "testdata/x509svid/good-leaf-and-intermediate.pem";
static String corrupted = "testdata/x509svid/corrupted";
static String keyECDSAOther = "testdata/x509svid/key-ecdsa-other.pem";
static String keyDER = "testdata/x509svid/keyEC.der";
static String certDER = "testdata/x509svid/cert.der";
@ -50,9 +48,11 @@ public class X509SvidTest {
.name("1. Single certificate and key")
.certsPath(certSingle)
.keyPath(keyRSA)
.expectedSpiffeId(SpiffeId.of(TrustDomain.of("example.org"), "workload-1"))
.hint("")
.expectedSpiffeId(SpiffeId.fromSegments(TrustDomain.parse("example.org"), "workload-1"))
.expectedNumberOfCerts(1)
.expectedPrivateKeyAlgorithm("RSA")
.expectedHint("")
.build()
),
Arguments.of(TestCase
@ -60,9 +60,11 @@ public class X509SvidTest {
.name("2. Certificate with intermediate and key")
.certsPath(certMultiple)
.keyPath(keyECDSA)
.expectedSpiffeId(SpiffeId.of(TrustDomain.of("example.org"), "workload-1"))
.hint("")
.expectedSpiffeId(SpiffeId.fromSegments(TrustDomain.parse("example.org"), "workload-1"))
.expectedNumberOfCerts(2)
.expectedPrivateKeyAlgorithm("EC")
.expectedHint("")
.build()
),
Arguments.of(TestCase
@ -70,7 +72,9 @@ public class X509SvidTest {
.name("3. Missing certificate")
.certsPath(keyRSA)
.keyPath(keyRSA)
.hint("")
.expectedError("Certificate could not be parsed from cert bytes")
.expectedHint("")
.build()
),
Arguments.of(TestCase
@ -78,6 +82,7 @@ public class X509SvidTest {
.name("4. Missing key")
.certsPath(certSingle)
.keyPath(certSingle)
.hint("")
.expectedError("Private Key could not be parsed from key bytes")
.build()
),
@ -86,6 +91,7 @@ public class X509SvidTest {
.name("5. Corrupted private key")
.certsPath(certSingle)
.keyPath(corrupted)
.hint("")
.expectedError("Private Key could not be parsed from key bytes")
.build()
),
@ -94,80 +100,84 @@ public class X509SvidTest {
.name("6. Corrupted certificate")
.certsPath(corrupted)
.keyPath(keyRSA)
.hint("")
.expectedError("Certificate could not be parsed from cert bytes")
.build()
),
Arguments.of(TestCase
.builder()
.name("7. Certificate does not match private key: RSA keys")
.certsPath(certSingle)
.keyPath(keyRSAOther)
.expectedError("Private Key does not match Certificate Public Key")
.build()
),
Arguments.of(TestCase
.builder()
.name("8. Certificate does not match private key: EC keys")
.certsPath(certMultiple)
.keyPath(keyECDSAOther)
.expectedError("Private Key does not match Certificate Public Key")
.build()
),
Arguments.of(TestCase
.builder()
.name("9. Certificate without SPIFFE ID")
.name("7. Certificate without SPIFFE ID")
.certsPath(leafEmptyID)
.keyPath(keyRSA)
.hint("")
.expectedError("Certificate does not contain SPIFFE ID in the URI SAN")
.build()
),
Arguments.of(TestCase
.builder()
.name("10. Leaf certificate with CA flag set to true")
.name("8. Leaf certificate with CA flag set to true")
.certsPath(leafCAtrue)
.keyPath(keyRSA)
.hint("")
.expectedError("Leaf certificate must not have CA flag set to true")
.build()
),
Arguments.of(TestCase
.builder()
.name("11. Leaf certificate without digitalSignature as key usage")
.name("9. Leaf certificate without digitalSignature as key usage")
.certsPath(leafNoDigitalSignature)
.keyPath(keyRSA)
.hint("")
.expectedError("Leaf certificate must have 'digitalSignature' as key usage")
.build()
),
Arguments.of(TestCase
.builder()
.name("12. Leaf certificate with certSign as key usage")
.name("10. Leaf certificate with certSign as key usage")
.certsPath(leafCertSign)
.keyPath(keyRSA)
.hint("")
.expectedError("Leaf certificate must not have 'keyCertSign' as key usage")
.build()
),
Arguments.of(TestCase
.builder()
.name("13. Leaf certificate with cRLSign as key usage")
.name("11. Leaf certificate with cRLSign as key usage")
.certsPath(leafCRLSign)
.keyPath(keyRSA)
.hint("")
.expectedError("Leaf certificate must not have 'cRLSign' as key usage")
.build()
),
Arguments.of(TestCase
.builder()
.name("14. Signing certificate without CA flag")
.name("12. Signing certificate without CA flag")
.certsPath(signNoCA)
.keyPath(keyRSA)
.hint("")
.expectedError("Signing certificate must have CA flag set to true")
.build()
),
Arguments.of(TestCase
.builder()
.name("15. Signing certificate without CA flag")
.name("13. Signing certificate without CA flag")
.certsPath(signNoKeyCertSign)
.keyPath(keyRSA)
.hint("")
.expectedError("Signing certificate must have 'keyCertSign' as key usage")
.build()
),
Arguments.of(TestCase
.builder()
.name("14. SVID with non-empty hint")
.certsPath(certSingle)
.keyPath(keyRSA)
.hint("internal")
.expectedSpiffeId(SpiffeId.fromSegments(TrustDomain.parse("example.org"), "workload-1"))
.expectedNumberOfCerts(1)
.expectedPrivateKeyAlgorithm("RSA")
.expectedHint("internal")
.build()
)
);
}
@ -194,8 +204,10 @@ public class X509SvidTest {
byte[] keyBytes = Files.readAllBytes(keyPath);
try {
X509Svid x509Svid = X509Svid.parseRaw(certBytes, keyBytes);
X509Svid x509Svid = X509Svid.parseRaw(certBytes, keyBytes, "external");
assertEquals("spiffe://example.org/workload-server", x509Svid.getSpiffeId().toString());
assertEquals("external", x509Svid.getHint());
} catch (X509SvidException e) {
fail(e);
}
@ -314,7 +326,7 @@ public class X509SvidTest {
byte[] certBytes = Files.readAllBytes(certPath);
byte[] keyBytes = Files.readAllBytes(keyPath);
X509Svid x509Svid = X509Svid.parse(certBytes, keyBytes);
X509Svid x509Svid = X509Svid.parse(certBytes, keyBytes, testCase.getHint());
if (StringUtils.isNotBlank(testCase.expectedError)) {
fail(String.format("Error was expected: %s", testCase.expectedError));
@ -327,6 +339,7 @@ public class X509SvidTest {
assertEquals(testCase.expectedNumberOfCerts, x509Svid.getChain().size());
assertEquals(testCase.expectedSpiffeId, x509Svid.getSpiffeId());
assertEquals(testCase.expectedPrivateKeyAlgorithm, x509Svid.getPrivateKey().getAlgorithm());
assertEquals(testCase.expectedHint, x509Svid.getHint());
} catch (Exception e) {
if (StringUtils.isBlank(testCase.expectedError)) {
@ -340,21 +353,25 @@ public class X509SvidTest {
static class TestCase {
String name;
String certsPath;
String hint;
String keyPath;
SpiffeId expectedSpiffeId;
int expectedNumberOfCerts;
String expectedPrivateKeyAlgorithm;
String expectedHint;
String expectedError;
@Builder
public TestCase(String name, String certsPath, String keyPath, SpiffeId expectedSpiffeId, int expectedNumberOfCerts, String expectedPrivateKeyAlgorithm, String expectedError) {
public TestCase(String name, String certsPath, String keyPath, String hint, SpiffeId expectedSpiffeId, int expectedNumberOfCerts, String expectedPrivateKeyAlgorithm, String expectedHint, String expectedError) {
this.name = name;
this.certsPath = certsPath;
this.keyPath = keyPath;
this.hint = hint;
this.expectedSpiffeId = expectedSpiffeId;
this.expectedNumberOfCerts = expectedNumberOfCerts;
this.expectedPrivateKeyAlgorithm = expectedPrivateKeyAlgorithm;
this.expectedError = expectedError;
this.expectedHint = expectedHint;
}
}
}

View File

@ -47,7 +47,7 @@ public class X509SvidValidatorTest {
x509Authorities.add(rootCa.getCertificate());
x509Authorities.add(otherRootCa.getCertificate());
val x509Bundle = new X509Bundle(TrustDomain.of("example.org"), x509Authorities);
val x509Bundle = new X509Bundle(TrustDomain.parse("example.org"), x509Authorities);
X509SvidValidator.verifyChain(chain, x509Bundle);
}
@ -56,7 +56,7 @@ public class X509SvidValidatorTest {
HashSet<X509Certificate> x509Authorities = new HashSet<>();
x509Authorities.add(otherRootCa.getCertificate());
val x509Bundle = new X509Bundle(TrustDomain.of("example.org"), x509Authorities);
val x509Bundle = new X509Bundle(TrustDomain.parse("example.org"), x509Authorities);
try {
X509SvidValidator.verifyChain(chain, x509Bundle);
fail("exception is expected");
@ -70,7 +70,7 @@ public class X509SvidValidatorTest {
HashSet<X509Certificate> x509Authorities = new HashSet<>();
x509Authorities.add(otherRootCa.getCertificate());
val x509Bundle = new X509Bundle(TrustDomain.of("other.org"), x509Authorities);
val x509Bundle = new X509Bundle(TrustDomain.parse("other.org"), x509Authorities);
try {
X509SvidValidator.verifyChain(chain, x509Bundle);
@ -138,7 +138,7 @@ public class X509SvidValidatorTest {
@Test
void verifyChain_nullChain_throwsNullPointerException() throws CertificateException, BundleNotFoundException {
try {
X509SvidValidator.verifyChain(null, new X509Bundle(TrustDomain.of("example.org")));
X509SvidValidator.verifyChain(null, new X509Bundle(TrustDomain.parse("example.org")));
fail("should have thrown an exception");
} catch (NullPointerException e) {
assertEquals("chain is marked non-null but is null", e.getMessage());

View File

@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
import java.net.URI;
import java.util.stream.Stream;
@ -74,19 +75,24 @@ public class AddressTest {
@Test
void getDefaultAddress() throws Exception {
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/test" );
String defaultAddress = Address.getDefaultAddress();
assertEquals("unix:/tmp/test", defaultAddress);
new EnvironmentVariables(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/test").execute(() -> {
String defaultAddress = Address.getDefaultAddress();
assertEquals("unix:/tmp/test", defaultAddress);
});
}
@Test
void getDefaultAddress_isBlankThrowsException() throws Exception {
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "");
try {
Address.getDefaultAddress();
fail();
} catch (Exception e) {
assertEquals("Endpoint Socket Address Environment Variable is not set: SPIFFE_ENDPOINT_SOCKET", e.getMessage());
}
new EnvironmentVariables(Address.SOCKET_ENV_VARIABLE, "").execute(() -> {
try {
Address.getDefaultAddress();
fail();
} catch (Exception e) {
assertEquals("Endpoint Socket Address Environment Variable is not set: SPIFFE_ENDPOINT_SOCKET", e.getMessage());
}
});
new EnvironmentVariables(Address.SOCKET_ENV_VARIABLE, "").execute(() -> {
});
}
}

View File

@ -0,0 +1,523 @@
package io.spiffe.workloadapi;
import com.google.common.collect.Sets;
import io.spiffe.bundle.jwtbundle.JwtBundle;
import io.spiffe.exception.BundleNotFoundException;
import io.spiffe.exception.JwtSourceException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.exception.SocketEndpointAddressException;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.spiffeid.TrustDomain;
import io.spiffe.svid.jwtsvid.JwtSvid;
import lombok.val;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
import java.io.IOException;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import static io.spiffe.workloadapi.WorkloadApiClientStub.JWT_TTL;
import static org.junit.jupiter.api.Assertions.*;
class CachedJwtSourceTest {
private CachedJwtSource jwtSource;
private WorkloadApiClientStub workloadApiClient;
private WorkloadApiClientErrorStub workloadApiClientErrorStub;
private Clock clock;
@BeforeEach
void setUp() throws JwtSourceException, SocketEndpointAddressException {
workloadApiClient = new WorkloadApiClientStub();
JwtSourceOptions options = JwtSourceOptions.builder().workloadApiClient(workloadApiClient).build();
System.setProperty(CachedJwtSource.TIMEOUT_SYSTEM_PROPERTY, "PT1S");
jwtSource = (CachedJwtSource) CachedJwtSource.newSource(options);
workloadApiClientErrorStub = new WorkloadApiClientErrorStub();
clock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
workloadApiClient.setClock(clock);
jwtSource.setClock(clock);
}
@AfterEach
void tearDown() throws IOException {
jwtSource.close();
}
@Test
void testGetBundleForTrustDomain() {
try {
JwtBundle bundle = jwtSource.getBundleForTrustDomain(TrustDomain.parse("example.org"));
assertNotNull(bundle);
assertEquals(TrustDomain.parse("example.org"), bundle.getTrustDomain());
} catch (BundleNotFoundException e) {
fail(e);
}
}
@Test
void testGetBundleForTrustDomain_nullParam() {
try {
jwtSource.getBundleForTrustDomain(null);
fail();
} catch (NullPointerException e) {
assertEquals("trustDomain is marked non-null but is null", e.getMessage());
} catch (BundleNotFoundException e) {
fail();
}
}
@Test
void testGetBundleForTrustDomain_SourceIsClosed_ThrowsIllegalStateException() throws IOException {
jwtSource.close();
try {
jwtSource.getBundleForTrustDomain(TrustDomain.parse("example.org"));
fail("expected exception");
} catch (IllegalStateException e) {
assertEquals("JWT bundle source is closed", e.getMessage());
assertTrue(workloadApiClient.closed);
} catch (BundleNotFoundException e) {
fail("not expected exception", e);
}
}
@Test
void testFetchJwtSvidWithSubject() {
try {
JwtSvid svid = jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidWithSubject_ReturnFromCache() {
try {
JwtSvid svid = jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/workload-server"), "aud3", "aud2", "aud1");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// call again to get from cache changing the order of the audiences
svid = jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// call again using different subject
svid = jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/extra-workload-server"), "aud2", "aud3", "aud1");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/extra-workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(2, workloadApiClient.getFetchJwtSvidCallCount());
// call again using the same audiences
svid = jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/extra-workload-server"), "aud1", "aud2", "aud3");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/extra-workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(2, workloadApiClient.getFetchJwtSvidCallCount());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidWithSubject_JwtSvidExpiredInCache() {
try {
JwtSvid svid = jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// set clock forwards but not enough to expire the JWT SVID in the cache
jwtSource.setClock(clock.offset(clock, JWT_TTL.dividedBy(2).minus(Duration.ofSeconds(1))));
// call again to get from cache, fetchJwtSvid call count should not change
svid = jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// set clock to expire the JWT SVID in the cache
jwtSource.setClock(clock.offset(clock, JWT_TTL.dividedBy(2).plus(Duration.ofSeconds(1))));
// call again, fetchJwtSvid call count should increase
svid = jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(2, workloadApiClient.getFetchJwtSvidCallCount());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidWithSubject_JwtSvidExpiredInCache_MultipleThreads() {
// test fetchJwtSvid with several threads trying to read and write the cache
// at the same time, the cache should be updated only once
try {
jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// set clock to expire the JWT SVID in the cache
Clock offset = Clock.offset(clock, JWT_TTL.dividedBy(2).plus(Duration.ofSeconds(1)));
jwtSource.setClock(offset);
workloadApiClient.setClock(offset);
// create a thread pool with 10 threads
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<JwtSvid>> futures = new ArrayList<>();
// create 10 tasks to fetch a JWT SVID
for (int i = 0; i < 10; i++) {
futures.add(executorService.submit(() -> jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3")));
}
// wait for all tasks to finish
for (Future<JwtSvid> future : futures) {
future.get();
}
// verify that the cache was updated only once after the JWT SVID expired
assertEquals(2, workloadApiClient.getFetchJwtSvidCallCount());
} catch (InterruptedException | ExecutionException | JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidWithoutSubject() {
try {
JwtSvid svid = jwtSource.fetchJwtSvid("aud1", "aud2", "aud3");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidWithoutSubject_ReturnFromCache() {
try {
JwtSvid svid = jwtSource.fetchJwtSvid("aud1", "aud2", "aud3");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// call again to get from cache changing the order of the audiences, the call count should not change
svid = jwtSource.fetchJwtSvid("aud3", "aud2", "aud1");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// call again using different audience, the call count should increase
svid = jwtSource.fetchJwtSvid("other-audience");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("other-audience"), svid.getAudience());
assertEquals(2, workloadApiClient.getFetchJwtSvidCallCount());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidWithoutSubject_JwtSvidExpiredInCache() {
try {
JwtSvid svid = jwtSource.fetchJwtSvid("aud1", "aud2", "aud3");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// set clock forwards but not enough to expire the JWT SVID in the cache
jwtSource.setClock(clock.offset(clock, JWT_TTL.dividedBy(2).minus(Duration.ofSeconds(1))));
// call again to get from cache, fetchJwtSvid call count should not change
svid = jwtSource.fetchJwtSvid("aud3", "aud2", "aud1");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// set clock forwards to expire the JWT SVID in the cache
jwtSource.setClock(clock.offset(clock, JWT_TTL.dividedBy(2).plus(Duration.ofSeconds(1))));
// call again, fetchJwtSvid call count should increase
svid = jwtSource.fetchJwtSvid("aud1", "aud2", "aud3");
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals(2, workloadApiClient.getFetchJwtSvidCallCount());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvid_SourceIsClosed_ThrowsIllegalStateException() throws IOException {
jwtSource.close();
try {
jwtSource.fetchJwtSvid("aud1", "aud2", "aud3");
fail("expected exception");
} catch (IllegalStateException e) {
assertEquals("JWT SVID source is closed", e.getMessage());
assertTrue(workloadApiClient.closed);
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidWithSubject_SourceIsClosed_ThrowsIllegalStateException() throws IOException {
jwtSource.close();
try {
jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
fail("expected exception");
} catch (IllegalStateException e) {
assertEquals("JWT SVID source is closed", e.getMessage());
assertTrue(workloadApiClient.closed);
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidsWithSubject() {
try {
List<JwtSvid> svids = jwtSource.fetchJwtSvids(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
assertNotNull(svids);
assertEquals(1, svids.size());
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svids.get(0).getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svids.get(0).getAudience());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidsWithSubject_ReturnFromCache() {
try {
List<JwtSvid> svids = jwtSource.fetchJwtSvids(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
assertNotNull(svids);
assertEquals(1, svids.size());
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svids.get(0).getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svids.get(0).getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// call again to get from cache changing the order of the audiences
svids = jwtSource.fetchJwtSvids(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
assertNotNull(svids);
assertEquals(1, svids.size());
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svids.get(0).getSpiffeId());
assertEquals(Sets.newHashSet("aud3", "aud2", "aud1"), svids.get(0).getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// call again using different audience
svids = jwtSource.fetchJwtSvids(SpiffeId.parse("spiffe://example.org/workload-server"), "other-audience");
assertNotNull(svids);
assertEquals(1, svids.size());
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svids.get(0).getSpiffeId());
assertEquals(Sets.newHashSet("other-audience"), svids.get(0).getAudience());
assertEquals(2, workloadApiClient.getFetchJwtSvidCallCount());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidsWithoutSubject() {
try {
List<JwtSvid> svids = jwtSource.fetchJwtSvids("aud1", "aud2", "aud3");
assertNotNull(svids);
assertEquals(svids.size(), 2);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svids.get(0).getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svids.get(0).getAudience());
assertEquals(SpiffeId.parse("spiffe://example.org/extra-workload-server"), svids.get(1).getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svids.get(1).getAudience());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidsWithoutSubject_ReturnFromCache() {
try {
List<JwtSvid> svids = jwtSource.fetchJwtSvids("aud1", "aud2", "aud3");
assertNotNull(svids);
assertEquals(svids.size(), 2);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svids.get(0).getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svids.get(0).getAudience());
assertEquals(SpiffeId.parse("spiffe://example.org/extra-workload-server"), svids.get(1).getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svids.get(1).getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// call again to get from cache changing the order of the audiences
svids = jwtSource.fetchJwtSvids("aud2", "aud3", "aud1");
assertNotNull(svids);
assertEquals(svids.size(), 2);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svids.get(0).getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svids.get(0).getAudience());
assertEquals(SpiffeId.parse("spiffe://example.org/extra-workload-server"), svids.get(1).getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svids.get(1).getAudience());
assertEquals(1, workloadApiClient.getFetchJwtSvidCallCount());
// call again using different audience
svids = jwtSource.fetchJwtSvids("other-audience");
assertNotNull(svids);
assertEquals(svids.size(), 2);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svids.get(0).getSpiffeId());
assertEquals(Sets.newHashSet("other-audience"), svids.get(0).getAudience());
assertEquals(SpiffeId.parse("spiffe://example.org/extra-workload-server"), svids.get(1).getSpiffeId());
assertEquals(Sets.newHashSet("other-audience"), svids.get(1).getAudience());
assertEquals(2, workloadApiClient.getFetchJwtSvidCallCount());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvids_SourceIsClosed_ThrowsIllegalStateException() throws IOException {
jwtSource.close();
try {
jwtSource.fetchJwtSvids("aud1", "aud2", "aud3");
fail("expected exception");
} catch (IllegalStateException e) {
assertEquals("JWT SVID source is closed", e.getMessage());
assertTrue(workloadApiClient.closed);
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidsWithSubject_SourceIsClosed_ThrowsIllegalStateException() throws IOException {
jwtSource.close();
try {
jwtSource.fetchJwtSvids(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
fail("expected exception");
} catch (IllegalStateException e) {
assertEquals("JWT SVID source is closed", e.getMessage());
assertTrue(workloadApiClient.closed);
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void newSource_success() {
val options = JwtSourceOptions
.builder()
.workloadApiClient(workloadApiClient)
.initTimeout(Duration.ofSeconds(0))
.build();
try {
JwtSource jwtSource = CachedJwtSource.newSource(options);
assertNotNull(jwtSource);
} catch (SocketEndpointAddressException | JwtSourceException e) {
fail(e);
}
}
@Test
void newSource_nullParam() {
try {
CachedJwtSource.newSource(null);
fail();
} catch (NullPointerException e) {
assertEquals("options is marked non-null but is null", e.getMessage());
} catch (SocketEndpointAddressException | JwtSourceException e) {
fail();
}
}
@Test
void newSource_errorFetchingJwtBundles() {
val options = JwtSourceOptions
.builder()
.workloadApiClient(workloadApiClientErrorStub)
.spiffeSocketPath("unix:/tmp/test")
.build();
try {
CachedJwtSource.newSource(options);
fail();
} catch (JwtSourceException e) {
assertEquals("Error creating JWT source", e.getMessage());
assertEquals("Error fetching JwtBundleSet", e.getCause().getMessage());
} catch (Exception e) {
fail();
}
}
@Test
void newSource_FailsBecauseOfTimeOut() throws Exception {
try {
val options = JwtSourceOptions
.builder()
.spiffeSocketPath("unix:/tmp/test")
.build();
CachedJwtSource.newSource(options);
fail();
} catch (JwtSourceException e) {
assertEquals("Error creating JWT source", e.getMessage());
assertEquals("Timeout waiting for JWT bundles update", e.getCause().getMessage());
} catch (SocketEndpointAddressException e) {
fail();
}
}
@Test
void newSource_DefaultSocketAddress() throws Exception {
new EnvironmentVariables(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/test").execute(() -> {
try {
CachedJwtSource.newSource();
fail();
} catch (JwtSourceException e) {
assertEquals("Error creating JWT source", e.getMessage());
} catch (SocketEndpointAddressException e) {
fail();
}
});
}
@Test
void newSource_noSocketAddress() throws Exception {
new EnvironmentVariables(Address.SOCKET_ENV_VARIABLE, "").execute(() -> {
try {
CachedJwtSource.newSource();
fail();
} catch (SocketEndpointAddressException e) {
fail();
} catch (IllegalStateException e) {
assertEquals("Endpoint Socket Address Environment Variable is not set: SPIFFE_ENDPOINT_SOCKET", e.getMessage());
}
});
}
}

View File

@ -14,16 +14,18 @@ import lombok.val;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
import java.io.IOException;
import java.time.Duration;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
class JwtSourceTest {
class DefaultJwtSourceTest {
private JwtSource jwtSource;
private WorkloadApiClientStub workloadApiClient;
@ -32,7 +34,7 @@ class JwtSourceTest {
@BeforeEach
void setUp() throws JwtSourceException, SocketEndpointAddressException {
workloadApiClient = new WorkloadApiClientStub();
DefaultJwtSource.JwtSourceOptions options = DefaultJwtSource.JwtSourceOptions.builder().workloadApiClient(workloadApiClient).build();
JwtSourceOptions options = JwtSourceOptions.builder().workloadApiClient(workloadApiClient).build();
System.setProperty(DefaultJwtSource.TIMEOUT_SYSTEM_PROPERTY, "PT1S");
jwtSource = DefaultJwtSource.newSource(options);
workloadApiClientErrorStub = new WorkloadApiClientErrorStub();
@ -46,9 +48,9 @@ class JwtSourceTest {
@Test
void testGetBundleForTrustDomain() {
try {
JwtBundle bundle = jwtSource.getBundleForTrustDomain(TrustDomain.of("example.org"));
JwtBundle bundle = jwtSource.getBundleForTrustDomain(TrustDomain.parse("example.org"));
assertNotNull(bundle);
assertEquals(TrustDomain.of("example.org"), bundle.getTrustDomain());
assertEquals(TrustDomain.parse("example.org"), bundle.getTrustDomain());
} catch (BundleNotFoundException e) {
fail(e);
}
@ -70,7 +72,7 @@ class JwtSourceTest {
void testGetBundleForTrustDomain_SourceIsClosed_ThrowsIllegalStateException() throws IOException {
jwtSource.close();
try {
jwtSource.getBundleForTrustDomain(TrustDomain.of("example.org"));
jwtSource.getBundleForTrustDomain(TrustDomain.parse("example.org"));
fail("expected exception");
} catch (IllegalStateException e) {
assertEquals("JWT bundle source is closed", e.getMessage());
@ -87,6 +89,7 @@ class JwtSourceTest {
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals("external", svid.getHint());
} catch (JwtSvidException e) {
fail(e);
}
@ -99,6 +102,7 @@ class JwtSourceTest {
assertNotNull(svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svid.getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svid.getAudience());
assertEquals("external", svid.getHint());
} catch (JwtSvidException e) {
fail(e);
}
@ -132,9 +136,67 @@ class JwtSourceTest {
}
}
@Test
void testFetchJwtSvidsWithSubject() {
try {
List<JwtSvid> svids = jwtSource.fetchJwtSvids(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
assertNotNull(svids);
assertEquals(svids.size(), 1);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svids.get(0).getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svids.get(0).getAudience());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidsWithoutSubject() {
try {
List<JwtSvid> svids = jwtSource.fetchJwtSvids("aud1", "aud2", "aud3");
assertNotNull(svids);
assertEquals(2, svids.size());
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), svids.get(0).getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svids.get(0).getAudience());
assertEquals(SpiffeId.parse("spiffe://example.org/extra-workload-server"), svids.get(1).getSpiffeId());
assertEquals(Sets.newHashSet("aud1", "aud2", "aud3"), svids.get(1).getAudience());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvids_SourceIsClosed_ThrowsIllegalStateException() throws IOException {
jwtSource.close();
try {
jwtSource.fetchJwtSvids("aud1", "aud2", "aud3");
fail("expected exception");
} catch (IllegalStateException e) {
assertEquals("JWT SVID source is closed", e.getMessage());
assertTrue(workloadApiClient.closed);
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidsWithSubject_SourceIsClosed_ThrowsIllegalStateException() throws IOException {
jwtSource.close();
try {
jwtSource.fetchJwtSvids(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
fail("expected exception");
} catch (IllegalStateException e) {
assertEquals("JWT SVID source is closed", e.getMessage());
assertTrue(workloadApiClient.closed);
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void newSource_success() {
val options = DefaultJwtSource.JwtSourceOptions
val options = JwtSourceOptions
.builder()
.workloadApiClient(workloadApiClient)
.initTimeout(Duration.ofSeconds(0))
@ -161,7 +223,7 @@ class JwtSourceTest {
@Test
void newSource_errorFetchingJwtBundles() {
val options = DefaultJwtSource.JwtSourceOptions
val options = JwtSourceOptions
.builder()
.workloadApiClient(workloadApiClientErrorStub)
.spiffeSocketPath("unix:/tmp/test")
@ -180,7 +242,7 @@ class JwtSourceTest {
@Test
void newSource_FailsBecauseOfTimeOut() throws Exception {
try {
val options = DefaultJwtSource.JwtSourceOptions
val options = JwtSourceOptions
.builder()
.spiffeSocketPath("unix:/tmp/test")
.build();
@ -196,28 +258,29 @@ class JwtSourceTest {
@Test
void newSource_DefaultSocketAddress() throws Exception {
try {
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/test");
DefaultJwtSource.newSource();
fail();
} catch (JwtSourceException e) {
assertEquals("Error creating JWT source", e.getMessage());
} catch (SocketEndpointAddressException e) {
fail();
}
new EnvironmentVariables(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/test").execute(() -> {
try {
DefaultJwtSource.newSource();
fail();
} catch (JwtSourceException e) {
assertEquals("Error creating JWT source", e.getMessage());
} catch (SocketEndpointAddressException e) {
fail();
}
});
}
@Test
void newSource_noSocketAddress() throws Exception {
try {
// just in case it's defined in the environment
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "");
DefaultJwtSource.newSource();
fail();
} catch (SocketEndpointAddressException e) {
fail();
} catch (IllegalStateException e) {
assertEquals("Endpoint Socket Address Environment Variable is not set: SPIFFE_ENDPOINT_SOCKET", e.getMessage());
}
new EnvironmentVariables(Address.SOCKET_ENV_VARIABLE, "").execute(() -> {
try {
DefaultJwtSource.newSource();
fail();
} catch (SocketEndpointAddressException e) {
fail();
} catch (IllegalStateException e) {
assertEquals("Endpoint Socket Address Environment Variable is not set: SPIFFE_ENDPOINT_SOCKET", e.getMessage());
}
});
}
}

View File

@ -2,8 +2,10 @@ package io.spiffe.workloadapi;
import io.grpc.testing.GrpcCleanupRule;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.JwtBundleException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.exception.X509BundleException;
import io.spiffe.exception.X509ContextException;
import io.spiffe.spiffeid.SpiffeId;
import org.junit.Rule;
@ -34,7 +36,7 @@ class DefaultWorkloadApiClientCorruptedResponsesTest {
}
@Test
public void testFetchX509Context_throwsX509ContextException() throws Exception {
void testFetchX509Context_throwsX509ContextException() throws Exception {
try {
workloadApiClient.fetchX509Context();
fail();
@ -44,9 +46,8 @@ class DefaultWorkloadApiClientCorruptedResponsesTest {
}
@Test
public void testWatchX509Context_onErrorIsCalledOnWatcher() throws Exception {
void testWatchX509Context_onErrorIsCalledOnWatcher() throws Exception {
CountDownLatch done = new CountDownLatch(1);
final String[] error = new String[1];
Watcher<X509Context> contextWatcher = new Watcher<X509Context>() {
@Override
public void onUpdate(X509Context update) {
@ -55,13 +56,41 @@ class DefaultWorkloadApiClientCorruptedResponsesTest {
@Override
public void onError(Throwable e) {
error[0] = e.getMessage();
assertEquals("Error processing X.509 Context update", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchX509Context(contextWatcher);
done.await();
assertEquals("Error processing X.509 Context update", error[0]);
}
@Test
void testFetchX509Bundles_throwsX509BundleException() {
try {
workloadApiClient.fetchX509Bundles();
fail();
} catch (X509BundleException e) {
assertEquals("Error fetching X.509 bundles", e.getMessage());
}
}
@Test
void testWatchX509Bundles_onErrorIsCalledOnWatched() throws InterruptedException {
CountDownLatch done = new CountDownLatch(1);
Watcher<X509BundleSet> contextWatcher = new Watcher<X509BundleSet>() {
@Override
public void onUpdate(X509BundleSet update) {
fail();
}
@Override
public void onError(Throwable e) {
assertEquals("Error processing X.509 bundles update", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchX509Bundles(contextWatcher);
done.await();
}
@Test
@ -97,7 +126,6 @@ class DefaultWorkloadApiClientCorruptedResponsesTest {
@Test
void testWatchJwtBundles_onErrorIsCalledOnWatched() throws InterruptedException {
CountDownLatch done = new CountDownLatch(1);
final String[] error = new String[1];
Watcher<JwtBundleSet> contextWatcher = new Watcher<JwtBundleSet>() {
@Override
public void onUpdate(JwtBundleSet update) {
@ -106,12 +134,11 @@ class DefaultWorkloadApiClientCorruptedResponsesTest {
@Override
public void onError(Throwable e) {
error[0] = e.getMessage();
assertEquals("Error processing JWT bundles update", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchJwtBundles(contextWatcher);
done.await();
assertEquals("Error processing JWT bundles update", error[0]);
}
}

View File

@ -2,8 +2,10 @@ package io.spiffe.workloadapi;
import io.grpc.testing.GrpcCleanupRule;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.JwtBundleException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.exception.X509BundleException;
import io.spiffe.exception.X509ContextException;
import io.spiffe.spiffeid.SpiffeId;
import org.junit.Rule;
@ -35,7 +37,7 @@ class DefaultWorkloadApiClientEmptyResponseTest {
@Test
public void testFetchX509Context_throwsX509ContextException() throws Exception {
void testFetchX509Context_throwsX509ContextException() throws Exception {
try {
workloadApiClient.fetchX509Context();
fail();
@ -45,9 +47,8 @@ class DefaultWorkloadApiClientEmptyResponseTest {
}
@Test
public void testWatchX509Context_onErrorIsCalledOnWatcher() throws Exception {
void testWatchX509Context_onErrorIsCalledOnWatcher() throws Exception {
CountDownLatch done = new CountDownLatch(1);
final String[] error = new String[1];
Watcher<X509Context> contextWatcher = new Watcher<X509Context>() {
@Override
public void onUpdate(X509Context update) {
@ -56,13 +57,41 @@ class DefaultWorkloadApiClientEmptyResponseTest {
@Override
public void onError(Throwable e) {
error[0] = e.getMessage();
assertEquals("Error processing X.509 Context update", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchX509Context(contextWatcher);
done.await();
assertEquals("Error processing X.509 Context update", error[0]);
}
@Test
void testFetchX509Bundles_throwsX509BundleException() {
try {
workloadApiClient.fetchX509Bundles();
fail();
} catch (X509BundleException e) {
assertEquals("Error fetching X.509 bundles", e.getMessage());
}
}
@Test
void testWatchX509Bundles_onErrorIsCalledOnWatched() throws InterruptedException {
CountDownLatch done = new CountDownLatch(1);
Watcher<X509BundleSet> contextWatcher = new Watcher<X509BundleSet>() {
@Override
public void onUpdate(X509BundleSet update) {
fail();
}
@Override
public void onError(Throwable e) {
assertEquals("Error processing X.509 bundles update", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchX509Bundles(contextWatcher);
done.await();
}
@Test
@ -87,6 +116,28 @@ class DefaultWorkloadApiClientEmptyResponseTest {
}
}
@Test
void testFetchJwtSvids_throwsJwtSvidException() {
try {
workloadApiClient.fetchJwtSvids("aud1", "aud2");
fail();
} catch (JwtSvidException e) {
assertEquals("Error fetching JWT SVID", e.getMessage());
assertEquals("JWT SVID response from the Workload API is empty", e.getCause().getMessage());
}
}
@Test
void testFetchJwtSvidsPassingSpiffeId_throwsJwtSvidException() {
try {
workloadApiClient.fetchJwtSvids(SpiffeId.parse("spiffe://example.org/test"), "aud1", "aud2");
fail();
} catch (JwtSvidException e) {
assertEquals("Error fetching JWT SVID", e.getMessage());
assertEquals("JWT SVID response from the Workload API is empty", e.getCause().getMessage());
}
}
@Test
void testValidateJwtSvid_throwsJwtSvidException() {
try {
@ -110,7 +161,6 @@ class DefaultWorkloadApiClientEmptyResponseTest {
@Test
void testWatchJwtBundles_onErrorIsCalledOnWatched() throws InterruptedException {
CountDownLatch done = new CountDownLatch(1);
final String[] error = new String[1];
Watcher<JwtBundleSet> contextWatcher = new Watcher<JwtBundleSet>() {
@Override
public void onUpdate(JwtBundleSet update) {
@ -119,12 +169,11 @@ class DefaultWorkloadApiClientEmptyResponseTest {
@Override
public void onError(Throwable e) {
error[0] = e.getMessage();
assertEquals("Error processing JWT bundles update", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchJwtBundles(contextWatcher);
done.await();
assertEquals("Error processing JWT bundles update", error[0]);
}
}

View File

@ -3,8 +3,10 @@ package io.spiffe.workloadapi;
import io.grpc.Status;
import io.grpc.testing.GrpcCleanupRule;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.JwtBundleException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.exception.X509BundleException;
import io.spiffe.exception.X509ContextException;
import io.spiffe.spiffeid.SpiffeId;
import org.junit.Rule;
@ -18,7 +20,7 @@ import java.util.concurrent.CountDownLatch;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
class DefaultWorkloadApiClientInvalidaArgumentTest {
class DefaultWorkloadApiClientInvalidArgumentTest {
@Rule
public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
@ -36,7 +38,7 @@ class DefaultWorkloadApiClientInvalidaArgumentTest {
@Test
public void testFetchX509Context_throwsX509ContextException() throws Exception {
void testFetchX509Context_throwsX509ContextException() throws Exception {
try {
workloadApiClient.fetchX509Context();
fail();
@ -46,9 +48,8 @@ class DefaultWorkloadApiClientInvalidaArgumentTest {
}
@Test
public void testWatchX509Context_onErrorIsCalledOnWatcher() throws Exception {
void testWatchX509Context_onErrorIsCalledOnWatcher() throws Exception {
CountDownLatch done = new CountDownLatch(1);
final String[] error = new String[1];
Watcher<X509Context> contextWatcher = new Watcher<X509Context>() {
@Override
public void onUpdate(X509Context update) {
@ -57,13 +58,41 @@ class DefaultWorkloadApiClientInvalidaArgumentTest {
@Override
public void onError(Throwable e) {
error[0] = e.getMessage();
assertEquals("Cancelling X.509 Context watch", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchX509Context(contextWatcher);
done.await();
assertEquals("Canceling X.509 Context watch", error[0]);
}
@Test
void testFetchX509Bundles_throwsX509BundleException() {
try {
workloadApiClient.fetchX509Bundles();
fail();
} catch (X509BundleException e) {
assertEquals("Error fetching X.509 bundles", e.getMessage());
}
}
@Test
void testWatchX509Bundles_onErrorIsCalledOnWatched() throws InterruptedException {
CountDownLatch done = new CountDownLatch(1);
Watcher<X509BundleSet> contextWatcher = new Watcher<X509BundleSet>() {
@Override
public void onUpdate(X509BundleSet update) {
fail();
}
@Override
public void onError(Throwable e) {
assertEquals("Cancelling X.509 bundles watch", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchX509Bundles(contextWatcher);
done.await();
}
@Test
@ -86,6 +115,26 @@ class DefaultWorkloadApiClientInvalidaArgumentTest {
}
}
@Test
void testFetchJwtSvids_throwsJwtSvidException() {
try {
workloadApiClient.fetchJwtSvids("aud1", "aud2");
fail();
} catch (JwtSvidException e) {
assertEquals("Error fetching JWT SVID", e.getMessage());
}
}
@Test
void testFetchJwtSvidsPassingSpiffeId_throwsJwtSvidException() {
try {
workloadApiClient.fetchJwtSvids(SpiffeId.parse("spiffe://example.org/test"), "aud1", "aud2");
fail();
} catch (JwtSvidException e) {
assertEquals("Error fetching JWT SVID", e.getMessage());
}
}
@Test
void testValidateJwtSvid_throwsJwtSvidException() {
try {
@ -109,7 +158,6 @@ class DefaultWorkloadApiClientInvalidaArgumentTest {
@Test
void testWatchJwtBundles_onErrorIsCalledOnWatched() throws InterruptedException {
CountDownLatch done = new CountDownLatch(1);
final String[] error = new String[1];
Watcher<JwtBundleSet> contextWatcher = new Watcher<JwtBundleSet>() {
@Override
public void onUpdate(JwtBundleSet update) {
@ -118,12 +166,11 @@ class DefaultWorkloadApiClientInvalidaArgumentTest {
@Override
public void onError(Throwable e) {
error[0] = e.getMessage();
assertEquals("Cancelling JWT Bundles watch", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchJwtBundles(contextWatcher);
done.await();
assertEquals("Canceling JWT Bundles watch", error[0]);
}
}

View File

@ -3,6 +3,7 @@ package io.spiffe.workloadapi;
import io.grpc.Status;
import io.grpc.testing.GrpcCleanupRule;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.X509ContextException;
import org.junit.Rule;
import org.junit.jupiter.api.AfterEach;
@ -34,7 +35,7 @@ class DefaultWorkloadApiClientRetryableErrorTest {
@Test
public void testFetchX509Context_throwsX509ContextException() throws Exception {
void testFetchX509Context_throwsX509ContextException() throws Exception {
try {
workloadApiClient.fetchX509Context();
fail();
@ -44,9 +45,8 @@ class DefaultWorkloadApiClientRetryableErrorTest {
}
@Test
public void testWatchX509Context_onErrorIsCalledOnWatcher() throws Exception {
void testWatchX509Context_onErrorIsCalledOnWatcher() throws Exception {
CountDownLatch done = new CountDownLatch(1);
final String[] error = new String[1];
Watcher<X509Context> contextWatcher = new Watcher<X509Context>() {
@Override
public void onUpdate(X509Context update) {
@ -55,19 +55,36 @@ class DefaultWorkloadApiClientRetryableErrorTest {
@Override
public void onError(Throwable e) {
error[0] = e.getMessage();
assertEquals("Cancelling X.509 Context watch", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchX509Context(contextWatcher);
done.await(5, TimeUnit.SECONDS);
assertEquals("Canceling X.509 Context watch", error[0]);
done.await();
}
@Test
void testWatchX509Bundles_onErrorIsCalledOnWatched() throws InterruptedException {
CountDownLatch done = new CountDownLatch(1);
Watcher<X509BundleSet> contextWatcher = new Watcher<X509BundleSet>() {
@Override
public void onUpdate(X509BundleSet update) {
fail();
}
@Override
public void onError(Throwable e) {
assertEquals("Cancelling X.509 bundles watch", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchX509Bundles(contextWatcher);
done.await();
}
@Test
void testWatchJwtBundles_onErrorIsCalledOnWatched() throws InterruptedException {
CountDownLatch done = new CountDownLatch(1);
final String[] error = new String[1];
Watcher<JwtBundleSet> contextWatcher = new Watcher<JwtBundleSet>() {
@Override
public void onUpdate(JwtBundleSet update) {
@ -76,12 +93,11 @@ class DefaultWorkloadApiClientRetryableErrorTest {
@Override
public void onError(Throwable e) {
error[0] = e.getMessage();
assertEquals("Cancelling JWT Bundles watch", e.getMessage());
done.countDown();
}
};
workloadApiClient.watchJwtBundles(contextWatcher);
done.await(5, TimeUnit.SECONDS);
assertEquals("Canceling JWT Bundles watch", error[0]);
done.await();
}
}

View File

@ -5,10 +5,12 @@ import io.grpc.testing.GrpcCleanupRule;
import io.spiffe.bundle.jwtbundle.JwtBundle;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509Bundle;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.BundleNotFoundException;
import io.spiffe.exception.JwtBundleException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.exception.SocketEndpointAddressException;
import io.spiffe.exception.X509BundleException;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.spiffeid.TrustDomain;
import io.spiffe.svid.jwtsvid.JwtSvid;
@ -18,6 +20,7 @@ import org.junit.Rule;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
import java.io.IOException;
import java.security.KeyPair;
@ -28,7 +31,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -53,13 +55,14 @@ class DefaultWorkloadApiClientTest {
@Test
void testNewClient_defaultOptions() throws Exception {
try {
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/agent.sock" );
WorkloadApiClient client = DefaultWorkloadApiClient.newClient();
assertNotNull(client);
} catch (SocketEndpointAddressException e) {
fail(e);
}
new EnvironmentVariables(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/agent.sock").execute(() -> {
try {
WorkloadApiClient client = DefaultWorkloadApiClient.newClient();
assertNotNull(client);
} catch (SocketEndpointAddressException e) {
fail(e);
}
});
}
@Test
@ -92,16 +95,17 @@ class DefaultWorkloadApiClientTest {
}
@Test
public void testFetchX509Context() throws Exception {
void testFetchX509Context() throws Exception {
X509Context x509Context = workloadApiClient.fetchX509Context();
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), x509Context.getDefaultSvid().getSpiffeId());
assertNotNull(x509Context.getDefaultSvid().getChain());
assertNotNull(x509Context.getDefaultSvid().getPrivateKey());
assertEquals("external", x509Context.getDefaultSvid().getHint());
assertNotNull(x509Context.getX509BundleSet());
try {
X509Bundle bundle = x509Context.getX509BundleSet().getBundleForTrustDomain(TrustDomain.of("example.org"));
X509Bundle bundle = x509Context.getX509BundleSet().getBundleForTrustDomain(TrustDomain.parse("example.org"));
assertNotNull(bundle);
} catch (BundleNotFoundException e) {
fail(e);
@ -126,16 +130,17 @@ class DefaultWorkloadApiClientTest {
};
workloadApiClient.watchX509Context(contextWatcher);
done.await(1, TimeUnit.SECONDS);
done.await();
X509Context update = x509Context[0];
assertNotNull(update);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), update.getDefaultSvid().getSpiffeId());
assertNotNull(update.getDefaultSvid().getChain());
assertNotNull(update.getDefaultSvid().getPrivateKey());
assertEquals("external", update.getDefaultSvid().getHint());
assertNotNull(update.getX509BundleSet());
try {
X509Bundle bundle = update.getX509BundleSet().getBundleForTrustDomain(TrustDomain.of("example.org"));
X509Bundle bundle = update.getX509BundleSet().getBundleForTrustDomain(TrustDomain.parse("example.org"));
assertNotNull(bundle);
} catch (BundleNotFoundException e) {
fail(e);
@ -151,6 +156,70 @@ class DefaultWorkloadApiClientTest {
}
}
@Test
void testFetchX509Bundles() {
X509BundleSet x509BundleSet = null;
try {
x509BundleSet = workloadApiClient.fetchX509Bundles();
} catch (X509BundleException e) {
fail(e);
}
assertNotNull(x509BundleSet);
try {
X509Bundle bundle = x509BundleSet.getBundleForTrustDomain(TrustDomain.parse("example.org"));
assertNotNull(bundle);
X509Bundle otherBundle = x509BundleSet.getBundleForTrustDomain(TrustDomain.parse("domain.test"));
assertNotNull(otherBundle);
} catch (BundleNotFoundException e) {
fail(e);
}
}
@Test
void testWatchX509Bundles() throws InterruptedException {
CountDownLatch done = new CountDownLatch(1);
final X509BundleSet[] x509BundleSet = new X509BundleSet[1];
Watcher<X509BundleSet> x509BundleSetWatcher = new Watcher<X509BundleSet>() {
@Override
public void onUpdate(X509BundleSet update) {
x509BundleSet[0] = update;
done.countDown();
}
@Override
public void onError(Throwable e) {
}
};
workloadApiClient.watchX509Bundles(x509BundleSetWatcher);
done.await();
X509BundleSet update = x509BundleSet[0];
assertNotNull(update);
try {
X509Bundle bundle1 = update.getBundleForTrustDomain(TrustDomain.parse("example.org"));
assertNotNull(bundle1);
X509Bundle bundle2 = update.getBundleForTrustDomain(TrustDomain.parse("domain.test"));
assertNotNull(bundle2);
} catch (BundleNotFoundException e) {
fail(e);
}
}
@Test
void testWatchX509BundlesNullWatcher_throwsNullPointerException() {
try {
workloadApiClient.watchX509Bundles(null);
} catch (NullPointerException e) {
assertEquals("watcher is marked non-null but is null", e.getMessage());
}
}
@Test
void testFetchJwtSvid() {
@ -160,6 +229,7 @@ class DefaultWorkloadApiClientTest {
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), jwtSvid.getSpiffeId());
assertTrue(jwtSvid.getAudience().contains("aud1"));
assertEquals(3, jwtSvid.getAudience().size());
assertEquals("external", jwtSvid.getHint());
} catch (JwtSvidException e) {
fail(e);
}
@ -173,6 +243,7 @@ class DefaultWorkloadApiClientTest {
assertEquals(SpiffeId.parse("spiffe://example.org/test"), jwtSvid.getSpiffeId());
assertTrue(jwtSvid.getAudience().contains("aud1"));
assertEquals(3, jwtSvid.getAudience().size());
assertEquals("external", jwtSvid.getHint());
} catch (JwtSvidException e) {
fail(e);
}
@ -214,6 +285,77 @@ class DefaultWorkloadApiClientTest {
}
}
@Test
void testFetchJwtSvids() {
try {
List<JwtSvid> jwtSvids = workloadApiClient.fetchJwtSvids("aud1", "aud2", "aud3");
System.out.println(jwtSvids.toString());
assertNotNull(jwtSvids);
assertEquals(jwtSvids.size(), 2);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), jwtSvids.get(0).getSpiffeId());
assertTrue(jwtSvids.get(0).getAudience().contains("aud1"));
assertEquals(3, jwtSvids.get(0).getAudience().size());
assertEquals("external", jwtSvids.get(0).getHint());
assertEquals(SpiffeId.parse("spiffe://example.org/extra-workload-server"), jwtSvids.get(1).getSpiffeId());
assertTrue(jwtSvids.get(1).getAudience().contains("aud1"));
assertEquals(3, jwtSvids.get(1).getAudience().size());
assertEquals("", jwtSvids.get(1).getHint());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvidsPassingSpiffeId() {
try {
List<JwtSvid> jwtSvids = workloadApiClient.fetchJwtSvids(SpiffeId.parse("spiffe://example.org/test"), "aud1", "aud2", "aud3");
assertNotNull(jwtSvids);
assertEquals(jwtSvids.size(), 1);
assertEquals(SpiffeId.parse("spiffe://example.org/test"), jwtSvids.get(0).getSpiffeId());
assertTrue(jwtSvids.get(0).getAudience().contains("aud1"));
assertEquals(3, jwtSvids.get(0).getAudience().size());
assertEquals("external", jwtSvids.get(0).getHint());
} catch (JwtSvidException e) {
fail(e);
}
}
@Test
void testFetchJwtSvids_nullAudience() {
try {
workloadApiClient.fetchJwtSvid(null, new String[]{"aud2", "aud3"});
fail();
} catch (NullPointerException e) {
assertEquals("audience is marked non-null but is null", e.getMessage());
} catch (JwtSvidException e) {
fail();
}
}
@Test
void testFetchJwtSvids_withSpiffeIdAndNullAudience() {
try {
workloadApiClient.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/text"), null, "aud2", "aud3");
fail();
} catch (NullPointerException e) {
assertEquals("audience is marked non-null but is null", e.getMessage());
} catch (JwtSvidException e) {
fail();
}
}
@Test
void testFetchJwtSvids_nullSpiffeId() {
try {
workloadApiClient.fetchJwtSvid(null, "aud1", new String[]{"aud2", "aud3"});
fail();
} catch (NullPointerException e) {
assertEquals("subject is marked non-null but is null", e.getMessage());
} catch (JwtSvidException e) {
fail();
}
}
@Test
void testValidateJwtSvid() {
String token = generateToken("spiffe://example.org/workload-server", Collections.singletonList("aud1"));
@ -262,7 +404,7 @@ class DefaultWorkloadApiClientTest {
assertNotNull(jwtBundleSet);
try {
JwtBundle bundle = jwtBundleSet.getBundleForTrustDomain(TrustDomain.of("example.org"));
JwtBundle bundle = jwtBundleSet.getBundleForTrustDomain(TrustDomain.parse("example.org"));
assertNotNull(bundle);
assertEquals(3, bundle.getJwtAuthorities().size());
} catch (BundleNotFoundException e) {
@ -283,18 +425,19 @@ class DefaultWorkloadApiClientTest {
done.countDown();
}
@Override
public void onError(Throwable e) {
}
};
workloadApiClient.watchJwtBundles(jwtBundleSetWatcher);
done.await(1, TimeUnit.SECONDS);
done.await();
JwtBundleSet update = jwtBundleSet[0];
assertNotNull(update);
try {
JwtBundle bundle = update.getBundleForTrustDomain(TrustDomain.of("example.org"));
JwtBundle bundle = update.getBundleForTrustDomain(TrustDomain.parse("example.org"));
assertNotNull(bundle);
assertEquals(3, bundle.getJwtAuthorities().size());
} catch (BundleNotFoundException e) {
@ -303,7 +446,7 @@ class DefaultWorkloadApiClientTest {
}
@Test
void testWatchSvidBundlesNullWatcher_throwsNullPointerException() {
void testWatchJwtBundlesNullWatcher_throwsNullPointerException() {
try {
workloadApiClient.watchJwtBundles(null);
} catch (NullPointerException e) {

View File

@ -7,11 +7,11 @@ import io.spiffe.exception.X509SourceException;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.spiffeid.TrustDomain;
import io.spiffe.svid.x509svid.X509Svid;
import io.spiffe.utils.TestUtils;
import lombok.val;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
import java.time.Duration;
@ -44,9 +44,9 @@ class DefaultX509SourceTest {
@Test
void testGetBundleForTrustDomain() {
try {
X509Bundle bundle = x509Source.getBundleForTrustDomain(TrustDomain.of("example.org"));
X509Bundle bundle = x509Source.getBundleForTrustDomain(TrustDomain.parse("example.org"));
assertNotNull(bundle);
assertEquals(TrustDomain.of("example.org"), bundle.getTrustDomain());
assertEquals(TrustDomain.parse("example.org"), bundle.getTrustDomain());
} catch (BundleNotFoundException e) {
fail(e);
}
@ -68,7 +68,7 @@ class DefaultX509SourceTest {
void testGetBundleForTrustDomain_SourceIsClosed_ThrowsIllegalStateExceptions() {
x509Source.close();
try {
x509Source.getBundleForTrustDomain(TrustDomain.of("example.org"));
x509Source.getBundleForTrustDomain(TrustDomain.parse("example.org"));
fail("exceptions is expected");
} catch (IllegalStateException e) {
assertEquals("X.509 bundle source is closed", e.getMessage());
@ -82,7 +82,8 @@ class DefaultX509SourceTest {
void testGetX509Svid() {
X509Svid x509Svid = x509Source.getX509Svid();
assertNotNull(x509Svid);
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"),x509Svid.getSpiffeId());
assertEquals(SpiffeId.parse("spiffe://example.org/workload-server"), x509Svid.getSpiffeId());
assertEquals("internal", x509Svid.getHint());
}
@Test
@ -123,6 +124,7 @@ class DefaultX509SourceTest {
fail();
}
}
@Test
void newSource_timeout() throws Exception {
try {
@ -141,7 +143,7 @@ class DefaultX509SourceTest {
}
@Test
void newSource_errorFetchingJwtBundles() {
void newSource_errorFetchingX509Context() {
val options = DefaultX509Source.X509SourceOptions
.builder()
.workloadApiClient(workloadApiClientErrorStub)
@ -160,15 +162,15 @@ class DefaultX509SourceTest {
@Test
void newSource_noSocketAddress() throws Exception {
try {
// just in case the variable is defined in the environment
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "");
DefaultX509Source.newSource();
fail();
} catch (X509SourceException | SocketEndpointAddressException e) {
fail();
} catch (IllegalStateException e) {
assertEquals("Endpoint Socket Address Environment Variable is not set: SPIFFE_ENDPOINT_SOCKET", e.getMessage());
}
new EnvironmentVariables(Address.SOCKET_ENV_VARIABLE, "").execute(() -> {
try {
DefaultX509Source.newSource();
fail();
} catch (X509SourceException | SocketEndpointAddressException e) {
fail();
} catch (IllegalStateException e) {
assertEquals("Endpoint Socket Address Environment Variable is not set: SPIFFE_ENDPOINT_SOCKET", e.getMessage());
}
});
}
}

View File

@ -66,12 +66,24 @@ class FakeWorkloadApi extends SpiffeWorkloadAPIImplBase {
.setX509Svid(svidByteString)
.setX509SvidKey(keyByteString)
.setBundle(bundleByteString)
.setHint("external")
.build();
// This X.509-SVID should be filtered out by the client because it has a non-unique hint and is not the first X.509-SVID in the response with this hint.
Workload.X509SVID skippedSVID = Workload.X509SVID
.newBuilder()
.setSpiffeId("spiffe://example.org/this0-should-be-filtered-out")
.setX509Svid(svidByteString)
.setX509SvidKey(keyByteString)
.setBundle(bundleByteString)
.setHint("external")
.build();
Workload.X509SVIDResponse response = Workload.X509SVIDResponse
.newBuilder()
.addSvids(svid)
.putFederatedBundles(TrustDomain.of("domain.test").getName(), federatedByteString)
.addSvids(skippedSVID)
.putFederatedBundles(TrustDomain.parse("domain.test").getName(), federatedByteString)
.build();
responseObserver.onNext(response);
@ -81,29 +93,86 @@ class FakeWorkloadApi extends SpiffeWorkloadAPIImplBase {
}
}
@Override
public void fetchX509Bundles(Workload.X509BundlesRequest request, StreamObserver<Workload.X509BundlesResponse> responseObserver) {
try {
Path pathBundle = Paths.get(toUri(x509Bundle));
byte[] bundleBytes = Files.readAllBytes(pathBundle);
ByteString bundleByteString = ByteString.copyFrom(bundleBytes);
Path pathFederateBundle = Paths.get(toUri(federatedBundle));
byte[] federatedBundleBytes = Files.readAllBytes(pathFederateBundle);
ByteString federatedByteString = ByteString.copyFrom(federatedBundleBytes);
Workload.X509BundlesResponse response = Workload.X509BundlesResponse
.newBuilder()
.putBundles(TrustDomain.parse("example.org").getName(), bundleByteString)
.putBundles(TrustDomain.parse("domain.test").getName(), federatedByteString)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
} catch (URISyntaxException | IOException e) {
throw new Error("Failed FakeSpiffeWorkloadApiService.fetchX509Bundles", e);
}
}
@Override
public void fetchJWTSVID(Workload.JWTSVIDRequest request, StreamObserver<Workload.JWTSVIDResponse> responseObserver) {
String spiffeId = request.getSpiffeId();
String extraSpiffeId = "spiffe://example.org/extra-workload-server";
String skippedSpiffeId = "spiffe://example.org/this-should-be-filtered-out";
boolean firstOnly = true;
if (StringUtils.isBlank(spiffeId)) {
firstOnly = false;
spiffeId = "spiffe://example.org/workload-server";
}
Date expiration = new Date(System.currentTimeMillis() + 3600000);
Map<String, Object> claims = new HashMap<>();
claims.put("sub", spiffeId);
claims.put("aud", getAudienceList(request.getAudienceList()));
Date expiration = new Date(System.currentTimeMillis() + 3600000);
claims.put("exp", expiration);
Map<String, Object> extraClaims = new HashMap<>();
extraClaims.put("sub", extraSpiffeId);
extraClaims.put("aud", getAudienceList(request.getAudienceList()));
extraClaims.put("exp", expiration);
KeyPair keyPair = TestUtils.generateECKeyPair(Curve.P_521);
String token = TestUtils.generateToken(claims, keyPair, "authority1");
String extraToken = TestUtils.generateToken(extraClaims, keyPair, "authority1");
Workload.JWTSVID jwtsvid = Workload.JWTSVID
.newBuilder()
.setSpiffeId(spiffeId)
.setSvid(token)
.setHint("external")
.build();
Workload.JWTSVIDResponse response = Workload.JWTSVIDResponse.newBuilder().addSvids(jwtsvid).build();
Workload.JWTSVID extraJwtsvid = Workload.JWTSVID
.newBuilder()
.setSpiffeId(extraSpiffeId)
.setSvid(extraToken)
.build();
// This JWT-SVID should be filtered out by the client because it has a non-unique hint and is not the first JWT-SVID in the response with this hint.
Workload.JWTSVID skippedJWTSVID = Workload.JWTSVID
.newBuilder()
.setSpiffeId(skippedSpiffeId)
.setSvid(extraToken)
.setHint("external")
.build();
Workload.JWTSVIDResponse.Builder builder = Workload.JWTSVIDResponse.newBuilder();
builder.addSvids(jwtsvid);
builder.addSvids(skippedJWTSVID);
if (!firstOnly) {
builder.addSvids(extraJwtsvid);
}
Workload.JWTSVIDResponse response = builder.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}

View File

@ -38,7 +38,7 @@ class FakeWorkloadApiCorruptedResponses extends SpiffeWorkloadAPIImplBase {
Workload.X509SVIDResponse response = Workload.X509SVIDResponse
.newBuilder()
.addSvids(svid)
.putFederatedBundles(TrustDomain.of("domain.test").getName(), corruptedByteString)
.putFederatedBundles(TrustDomain.parse("domain.test").getName(), corruptedByteString)
.build();
responseObserver.onNext(response);
@ -48,6 +48,25 @@ class FakeWorkloadApiCorruptedResponses extends SpiffeWorkloadAPIImplBase {
}
}
@Override
public void fetchX509Bundles(Workload.X509BundlesRequest request, StreamObserver<Workload.X509BundlesResponse> responseObserver) {
Path pathBundle = null;
try {
pathBundle = Paths.get(toUri(corrupted));
byte[] bundleBytes = Files.readAllBytes(pathBundle);
ByteString corruptedByteString = ByteString.copyFrom(bundleBytes);
Workload.X509BundlesResponse response = Workload.X509BundlesResponse
.newBuilder()
.putBundles("example.org", corruptedByteString)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
} catch (URISyntaxException | IOException e) {
throw new Error("Failed FakeSpiffeWorkloadApiService.fetchX509Bundles", e);
}
}
@Override
public void fetchJWTSVID(Workload.JWTSVIDRequest request, StreamObserver<Workload.JWTSVIDResponse> responseObserver) {

View File

@ -12,6 +12,12 @@ class FakeWorkloadApiEmptyResponse extends SpiffeWorkloadAPIImplBase {
responseObserver.onCompleted();
}
@Override
public void fetchX509Bundles(Workload.X509BundlesRequest request, StreamObserver<Workload.X509BundlesResponse> responseObserver) {
responseObserver.onNext(Workload.X509BundlesResponse.newBuilder().build());
responseObserver.onCompleted();
}
@Override
public void fetchJWTSVID(Workload.JWTSVIDRequest request, StreamObserver<Workload.JWTSVIDResponse> responseObserver) {
responseObserver.onNext(Workload.JWTSVIDResponse.newBuilder().build());

View File

@ -20,6 +20,11 @@ class FakeWorkloadApiExceptions extends SpiffeWorkloadAPIImplBase {
responseObserver.onError(exception);
}
@Override
public void fetchX509Bundles(Workload.X509BundlesRequest request, StreamObserver<Workload.X509BundlesResponse> responseObserver) {
responseObserver.onError(exception);
}
@Override
public void fetchJWTSVID(Workload.JWTSVIDRequest request, StreamObserver<Workload.JWTSVIDResponse> responseObserver) {
responseObserver.onError(exception);

View File

@ -1,20 +1,36 @@
package io.spiffe.workloadapi;
import com.google.protobuf.ByteString;
import io.spiffe.bundle.x509bundle.X509Bundle;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.BundleNotFoundException;
import io.spiffe.exception.JwtBundleException;
import io.spiffe.exception.X509BundleException;
import io.spiffe.exception.X509ContextException;
import io.spiffe.spiffeid.TrustDomain;
import io.spiffe.workloadapi.grpc.Workload;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Iterator;
import static io.spiffe.utils.TestUtils.toUri;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
class GrpcConversionUtilsTest {
final String x509Bundle = "testdata/workloadapi/bundle.der";
final String federatedBundle = "testdata/workloadapi/federated-bundle.pem";
@Test
void toX509Context_emptyResponse() {
void test_toX509Context_emptyResponse() {
Iterator<Workload.X509SVIDResponse> iterator = Collections.emptyIterator();
try {
GrpcConversionUtils.toX509Context(iterator);
@ -24,21 +40,92 @@ class GrpcConversionUtilsTest {
}
@Test
void toBundleSet() {
void test_toJwtBundleSet_emtpyResponse() {
Iterator<Workload.JWTBundlesResponse> iterator = Collections.emptyIterator();
try {
GrpcConversionUtils.toBundleSet(iterator);
GrpcConversionUtils.toJwtBundleSet(iterator);
} catch (JwtBundleException e) {
assertEquals("JWT Bundle response from the Workload API is empty", e.getMessage());
}
}
@Test
void parseX509Bundle_corruptedBytes() {
void test_parseX509Bundle_corruptedBytes() {
try {
GrpcConversionUtils.parseX509Bundle(TrustDomain.of("example.org"), "corrupted".getBytes());
GrpcConversionUtils.parseX509Bundle(TrustDomain.parse("example.org"), "corrupted".getBytes());
} catch (X509ContextException e) {
assertEquals("X.509 Bundles could not be processed", e.getMessage());
}
}
@Test
void test_toX509BundleSet_from_X509BundlesResponse() throws URISyntaxException, IOException {
Workload.X509BundlesResponse response = createX509BundlesResponse();
try {
X509BundleSet x509BundleSet = GrpcConversionUtils.toX509BundleSet(response);
X509Bundle bundle1 = x509BundleSet.getBundleForTrustDomain(TrustDomain.parse("example.org"));
X509Bundle bundle2 = x509BundleSet.getBundleForTrustDomain(TrustDomain.parse("domain.test"));
assertEquals(1, bundle1.getX509Authorities().size());
assertEquals(1, bundle2.getX509Authorities().size());
} catch (X509BundleException | BundleNotFoundException e) {
fail();
}
}
@Test
void test_toX509BundleSet_from_X509BundlesResponseIterator() throws URISyntaxException, IOException {
Workload.X509BundlesResponse response = createX509BundlesResponse();
final Iterator<Workload.X509BundlesResponse> iterator = Collections.singleton(response).iterator();
try {
X509BundleSet x509BundleSet = GrpcConversionUtils.toX509BundleSet(iterator);
X509Bundle bundle1 = x509BundleSet.getBundleForTrustDomain(TrustDomain.parse("example.org"));
X509Bundle bundle2 = x509BundleSet.getBundleForTrustDomain(TrustDomain.parse("domain.test"));
assertEquals(1, bundle1.getX509Authorities().size());
assertEquals(1, bundle2.getX509Authorities().size());
} catch (X509BundleException | BundleNotFoundException e) {
fail();
}
}
@Test
void test_toX509BundleSet_fromEmptyResponse() {
Workload.X509BundlesResponse response = Workload.X509BundlesResponse.newBuilder().build();
try {
GrpcConversionUtils.toX509BundleSet(response);
fail();
} catch (X509BundleException e) {
assertEquals("X.509 Bundle response from the Workload API is empty", e.getMessage());
}
}
@Test
void test_toX509BundleSet_fromEmptyIterator() {
final Iterator<Workload.X509BundlesResponse> iterator = Collections.emptyListIterator();
try {
GrpcConversionUtils.toX509BundleSet(iterator);
fail();
} catch (X509BundleException e) {
assertEquals("X.509 Bundle response from the Workload API is empty", e.getMessage());
}
}
private Workload.X509BundlesResponse createX509BundlesResponse() throws URISyntaxException, IOException {
Path pathBundle = Paths.get(toUri(x509Bundle));
byte[] bundleBytes = Files.readAllBytes(pathBundle);
ByteString bundleByteString = ByteString.copyFrom(bundleBytes);
Path pathFederateBundle = Paths.get(toUri(federatedBundle));
byte[] federatedBundleBytes = Files.readAllBytes(pathFederateBundle);
ByteString federatedByteString = ByteString.copyFrom(federatedBundleBytes);
return Workload.X509BundlesResponse
.newBuilder()
.putBundles(TrustDomain.parse("example.org").getName(), bundleByteString)
.putBundles(TrustDomain.parse("domain.test").getName(), federatedByteString)
.build();
}
}

View File

@ -1,14 +1,17 @@
package io.spiffe.workloadapi;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.JwtBundleException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.exception.X509BundleException;
import io.spiffe.exception.X509ContextException;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.svid.jwtsvid.JwtSvid;
import lombok.NonNull;
import java.io.IOException;
import java.util.List;
public class WorkloadApiClientErrorStub implements WorkloadApiClient {
@ -22,6 +25,16 @@ public class WorkloadApiClientErrorStub implements WorkloadApiClient {
watcher.onError(new X509ContextException("Testing exception"));
}
@Override
public X509BundleSet fetchX509Bundles() throws X509BundleException {
throw new X509BundleException("Testing exception");
}
@Override
public void watchX509Bundles(@NonNull Watcher<X509BundleSet> watcher) {
watcher.onError(new X509BundleException("Testing exception"));
}
@Override
public JwtSvid fetchJwtSvid(@NonNull final String audience, final String... extraAudience) throws JwtSvidException {
throw new JwtSvidException("Testing exception");
@ -32,6 +45,16 @@ public class WorkloadApiClientErrorStub implements WorkloadApiClient {
throw new JwtSvidException("Testing exception");
}
@Override
public List<JwtSvid> fetchJwtSvids(@NonNull String audience, String... extraAudience) throws JwtSvidException {
throw new JwtSvidException("Testing exception");
}
@Override
public List<JwtSvid> fetchJwtSvids(@NonNull SpiffeId subject, @NonNull String audience, String... extraAudience) throws JwtSvidException {
throw new JwtSvidException("Testing exception");
}
@Override
public JwtBundleSet fetchJwtBundles() throws JwtBundleException {
throw new JwtBundleException("Testing exception");

View File

@ -23,26 +23,28 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.time.Clock;
import java.time.Duration;
import java.util.*;
import static io.spiffe.utils.TestUtils.toUri;
public class WorkloadApiClientStub implements WorkloadApiClient {
static final Duration JWT_TTL = Duration.ofSeconds(60);
final String privateKey = "testdata/workloadapi/svid.key.der";
final String svid = "testdata/workloadapi/svid.der";
final String x509Bundle = "testdata/workloadapi/bundle.der";
final String jwtBundle = "testdata/workloadapi/bundle.json";
final SpiffeId subject = SpiffeId.parse("spiffe://example.org/workload-server");
final SpiffeId extraSubject = SpiffeId.parse("spiffe://example.org/extra-workload-server");
int fetchJwtSvidCallCount = 0;
boolean closed;
Clock clock = Clock.systemDefaultZone();
@Override
public X509Context fetchX509Context() {
return generateX509Context();
@ -54,16 +56,46 @@ public class WorkloadApiClientStub implements WorkloadApiClient {
watcher.onUpdate(update);
}
@Override
public X509BundleSet fetchX509Bundles() {
return generateX509BundleSet();
}
@Override
public void watchX509Bundles(@NonNull Watcher<X509BundleSet> watcher) {
val x509BundleSet = generateX509BundleSet();
watcher.onUpdate(x509BundleSet);
}
@Override
public JwtSvid fetchJwtSvid(@NonNull final String audience, final String... extraAudience) throws JwtSvidException {
fetchJwtSvidCallCount++;
return generateJwtSvid(subject, audience, extraAudience);
}
@Override
public JwtSvid fetchJwtSvid(@NonNull final SpiffeId subject, @NonNull final String audience, final String... extraAudience) throws JwtSvidException {
fetchJwtSvidCallCount++;
return generateJwtSvid(subject, audience, extraAudience);
}
@Override
public List<JwtSvid> fetchJwtSvids(@NonNull String audience, String... extraAudience) throws JwtSvidException {
fetchJwtSvidCallCount++;
List<JwtSvid> svids = new ArrayList<>();
svids.add(generateJwtSvid(subject, audience, extraAudience));
svids.add(generateJwtSvid(extraSubject, audience, extraAudience));
return svids;
}
@Override
public List<JwtSvid> fetchJwtSvids(@NonNull SpiffeId subject, @NonNull String audience, String... extraAudience) throws JwtSvidException {
fetchJwtSvidCallCount++;
List<JwtSvid> svids = new ArrayList<>();
svids.add(generateJwtSvid(subject, audience, extraAudience));
return svids;
}
@Override
public JwtBundleSet fetchJwtBundles() throws JwtBundleException {
return generateJwtBundleSet();
@ -84,13 +116,25 @@ public class WorkloadApiClientStub implements WorkloadApiClient {
try {
val pathBundle = Paths.get(toUri(jwtBundle));
byte[] bundleBytes = Files.readAllBytes(pathBundle);
val jwtBundle = JwtBundle.parse(TrustDomain.of("example.org"), bundleBytes);
val jwtBundle = JwtBundle.parse(TrustDomain.parse("example.org"), bundleBytes);
return JwtBundleSet.of(Collections.singleton(jwtBundle));
} catch (IOException | JwtBundleException | URISyntaxException e) {
throw new RuntimeException(e);
}
}
private X509BundleSet generateX509BundleSet() {
try {
val pathBundle = Paths.get(toUri(x509Bundle));
byte[] bundleBytes = Files.readAllBytes(pathBundle);
val x509Bundle1 = X509Bundle.parse(TrustDomain.parse("example.org"), bundleBytes);
val x509Bundle2 = X509Bundle.parse(TrustDomain.parse("domain.test"), bundleBytes);
return X509BundleSet.of(Arrays.asList(x509Bundle1, x509Bundle2));
} catch (IOException | X509BundleException | URISyntaxException e) {
throw new RuntimeException(e);
}
}
private JwtSvid generateJwtSvid(final @NonNull SpiffeId subject, final @NonNull String audience, final String[] extraAudience) throws JwtSvidException {
final Set<String> audParam = new HashSet<>();
audParam.add(audience);
@ -99,14 +143,15 @@ public class WorkloadApiClientStub implements WorkloadApiClient {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", subject.toString());
claims.put("aud", new ArrayList<>(audParam));
Date expiration = new Date(System.currentTimeMillis() + 3600000);
claims.put("exp", expiration);
claims.put("iat", new Date(clock.millis()));
claims.put("exp", new Date(clock.millis() + JWT_TTL.toMillis()));
KeyPair keyPair = TestUtils.generateECKeyPair(Curve.P_521);
String token = TestUtils.generateToken(claims, keyPair, "authority1");
return JwtSvid.parseInsecure(token, audParam);
return JwtSvid.parseInsecure(token, audParam, "external");
}
@ -126,7 +171,7 @@ public class WorkloadApiClientStub implements WorkloadApiClient {
try {
Path pathBundle = Paths.get(toUri(x509Bundle));
byte[] bundleBytes = Files.readAllBytes(pathBundle);
return X509Bundle.parse(TrustDomain.of("example.org"), bundleBytes);
return X509Bundle.parse(TrustDomain.parse("example.org"), bundleBytes);
} catch (IOException | URISyntaxException | X509BundleException e) {
throw new RuntimeException(e);
}
@ -140,9 +185,25 @@ public class WorkloadApiClientStub implements WorkloadApiClient {
Path pathKey = Paths.get(toUri(privateKey));
byte[] keyBytes = Files.readAllBytes(pathKey);
return X509Svid.parseRaw(svidBytes, keyBytes);
return X509Svid.parseRaw(svidBytes, keyBytes, "internal");
} catch (X509SvidException | IOException | URISyntaxException e) {
throw new RuntimeException(e);
}
}
void resetFetchJwtSvidCallCount() {
fetchJwtSvidCallCount = 0;
}
int getFetchJwtSvidCallCount() {
return fetchJwtSvidCallCount;
}
Clock getClock() {
return clock;
}
void setClock(Clock clock) {
this.clock = clock;
}
}

View File

@ -45,8 +45,8 @@ class X509ContextTest {
}
private X509BundleSet createBundleSet() {
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.of("example.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.of("other.org"));
X509Bundle x509Bundle1 = new X509Bundle(TrustDomain.parse("example.org"));
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.parse("other.org"));
List<X509Bundle> bundleList = Arrays.asList(x509Bundle1, x509Bundle2);
return X509BundleSet.of(bundleList);
}

View File

@ -11,8 +11,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.*;
class RetryHandlerTest {
@ -78,7 +77,8 @@ class RetryHandlerTest {
// fourth retry exceeds max retries
retryHandler.scheduleRetry(runnable);
verifyNoInteractions(scheduledExecutorService);
verify(scheduledExecutorService).isShutdown();
verifyNoMoreInteractions(scheduledExecutorService);
}
@Test

View File

@ -1,6 +1,7 @@
package io.spiffe.utils;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
@ -9,6 +10,7 @@ import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import io.spiffe.svid.jwtsvid.JwtSvid;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@ -57,10 +59,14 @@ public class TestUtils {
public static String generateToken(Map<String, Object> claims, KeyPair keyPair, String keyId) {
JWTClaimsSet jwtClaimsSet = buildJWTClaimSetFromClaimsMap(claims);
return generateToken(jwtClaimsSet, keyPair, keyId);
return generateToken(jwtClaimsSet, keyPair, keyId, JwtSvid.HEADER_TYP_JWT);
}
public static String generateToken(JWTClaimsSet claims, KeyPair keyPair, String keyId) {
return generateToken(claims, keyPair, keyId, JwtSvid.HEADER_TYP_JWT);
}
public static String generateToken(JWTClaimsSet claims, KeyPair keyPair, String keyId, String typ) {
try {
JWSAlgorithm algorithm;
JWSSigner signer;
@ -74,7 +80,9 @@ public class TestUtils {
throw new IllegalArgumentException("Algorithm not supported");
}
SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(algorithm).keyID(keyId).build(), claims);
final JOSEObjectType joseTyp = new JOSEObjectType(typ);
final JWSHeader header = new JWSHeader.Builder(algorithm).keyID(keyId).type(joseTyp).build();
SignedJWT signedJWT = new SignedJWT(header, claims);
signedJWT.sign(signer);
return signedJWT.serialize();
} catch (JOSEException e) {
@ -93,43 +101,13 @@ public class TestUtils {
public static JWTClaimsSet buildJWTClaimSetFromClaimsMap(Map<String, Object> claims) {
return new JWTClaimsSet.Builder()
.subject((String) claims.get("sub"))
.issueTime((Date) claims.get("iat"))
.expirationTime((Date) claims.get("exp"))
.audience((List<String>) claims.get("aud"))
.build();
}
public static void setEnvironmentVariable(String variableName, String value) throws Exception {
Class<?> processEnvironment = Class.forName("java.lang.ProcessEnvironment");
Field unmodifiableMapField = getField(processEnvironment, "theUnmodifiableEnvironment");
Object unmodifiableMap = unmodifiableMapField.get(null);
injectIntoUnmodifiableMap(variableName, value, unmodifiableMap);
Field mapField = getField(processEnvironment, "theEnvironment");
Map<String, String> map = (Map<String, String>) mapField.get(null);
map.put(variableName, value);
}
public static Object invokeMethod(Class<?> clazz, String methodName, Object... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
return method.invoke(args);
}
public static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
}
public static URI toUri(String path) throws URISyntaxException {
return Thread.currentThread().getContextClassLoader().getResource(path).toURI();
}
private static void injectIntoUnmodifiableMap(String key, String value, Object map) throws ReflectiveOperationException {
Class unmodifiableMap = Class.forName("java.util.Collections$UnmodifiableMap");
Field field = getField(unmodifiableMap, "m");
Object obj = field.get(map);
((Map<String, String>) obj).put(key, value);
}
}

View File

@ -10,15 +10,14 @@ The Helper automatically gets the SVID updates and stores them in the KeyStore a
On Linux:
`java -jar java-spiffe-helper-0.6.2-linux-x86_64.jar -c helper.conf`
`java -jar java-spiffe-helper-0.8.12-linux-x86_64.jar`
On Mac OS:
`java -jar java-spiffe-helper-0.6.2-osx-x86_64.jar -c helper.conf`
`java -jar java-spiffe-helper-0.8.12-osx-x86_64.jar`
(The jar can be found in `build/libs`, after running the gradle build)
Either `-c` or `--config` should be used to pass the path to the config file.
You can run the utility with the `-c` or `--config` option to specify the path to the configuration file. By default, it
will look for a configuration file named `conf/java-spiffe-helper.properties` in the current working directory.
## Config file
@ -39,20 +38,19 @@ spiffeSocketPath = unix:/tmp/agent.sock
### Configuration Properties
|Configuration | Description | Default value |
|------------------|--------------------------------------------------------------------------------| ------------- |
|`keyStorePath` | Path to the Java KeyStore File for storing the Private Key and chain of certs | none |
|`keyStorePass` | Password to protect the Java KeyStore File | none |
|`keyPass` | Password to protect the Private Key entry in the KeyStore | none |
|`trustStorePath` | Path to the Java TrustStore File for storing the trusted bundles | none |
|`trustStorePass` | Password to protect the Java TrustStore File | none |
|`keyStoreType` | Java KeyStore Type. (`pkcs12` and `jks` are supported). Case insensitive. | pkcs12 |
|`keyAlias` | Alias for the Private Key entry | spiffe |
|`spiffeSocketPath`| Path the Workload API | Read from the system variable: SPIFFE_ENDPOINT_SOCKET |
KeyStore and TrustStore **must** be in separate files. If `keyStorePath` and `trustStorePath` points to the same file, an error
is shown
.
| Configuration | Description | Default value |
|--------------------|-------------------------------------------------------------------------------|-------------------------------------------------------|
| `keyStorePath` | Path to the Java KeyStore File for storing the Private Key and chain of certs | none |
| `keyStorePass` | Password to protect the Java KeyStore File | none |
| `keyPass` | Password to protect the Private Key entry in the KeyStore | none |
| `trustStorePath` | Path to the Java TrustStore File for storing the trusted bundles | none |
| `trustStorePass` | Password to protect the Java TrustStore File | none |
| `keyStoreType` | Java KeyStore Type. (`pkcs12` and `jks` are supported). Case insensitive. | pkcs12 |
| `keyAlias` | Alias for the Private Key entry | spiffe |
| `spiffeSocketPath` | Path the Workload API | Read from the system variable: SPIFFE_ENDPOINT_SOCKET |
KeyStore and TrustStore **must** be in separate files. If `keyStorePath` and `trustStorePath` points to the same file,
an error is shown.
If the store files do not exist, they are created.
The default and **recommended KeyStore Type** is `PKCS12`. The same type is used for both KeyStore and TrustStore.

View File

@ -1,5 +1,5 @@
plugins {
id "com.github.johnrengelman.shadow" version "5.2.0"
id "com.github.johnrengelman.shadow" version "${shadowVersion}"
}
description = "Java SPIFFE Library Helper module to store X.509 SVIDs and Bundles in a Java KeyStore in disk"
@ -9,7 +9,8 @@ apply plugin: 'com.github.johnrengelman.shadow'
assemble.dependsOn shadowJar
shadowJar {
archiveClassifier = osdetector.classifier
mergeServiceFiles()
archiveClassifier = project.hasProperty('archiveClassifier') && project.archiveClassifier != "" ? project.archiveClassifier : osdetector.classifier
manifest {
attributes 'Main-Class': 'io.spiffe.helper.cli.Runner'
}
@ -19,13 +20,20 @@ dependencies {
api(project(':java-spiffe-core'))
// runtimeOnly grpc-netty dependency module will be included in the shadowJar
if (gradle.ext.isMacOsX) {
runtimeOnly(project(':java-spiffe-core:grpc-netty-macos'))
if (osdetector.os.is('osx') ) {
project.ext.osArch = System.getProperty("os.arch")
if ("x86_64" == project.ext.osArch) {
runtimeOnly(project(':java-spiffe-core:grpc-netty-macos'))
} else if ("aarch64" == project.ext.osArch) {
runtimeOnly(project(':java-spiffe-core:grpc-netty-macos-aarch64'))
} else {
throw new GradleException("Architecture not supported: " + project.ext.osArch)
}
} else {
runtimeOnly(project(':java-spiffe-core:grpc-netty-linux'))
}
implementation group: 'commons-cli', name: 'commons-cli', version: '1.4'
implementation group: 'commons-cli', name: 'commons-cli', version: '1.9.0'
testImplementation(testFixtures(project(":java-spiffe-core")))
}

View File

@ -0,0 +1 @@
archiveClassifier=

View File

@ -4,11 +4,7 @@ import io.spiffe.helper.exception.RunnerException;
import io.spiffe.helper.keystore.KeyStoreHelper.KeyStoreOptions;
import io.spiffe.helper.keystore.KeyStoreType;
import lombok.val;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.*;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
@ -16,17 +12,18 @@ import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidParameterException;
import java.util.Properties;
class Config {
private static final String DEFAULT_CONFIG_FILENAME = "conf/java-spiffe-helper.properties";
static final Option CONFIG_FILE_OPTION =
Option.builder("c")
.longOpt("config")
.hasArg(true)
.required(true)
.build();
.longOpt("config")
.hasArg(true)
.required(false)
.build();
private Config() {
}
@ -42,17 +39,17 @@ class Config {
return properties;
}
static String getCliConfigOption(final String... args) throws RunnerException {
static String getCliConfigOption(final String... args) throws ParseException {
final Options cliOptions = new Options();
cliOptions.addOption(CONFIG_FILE_OPTION);
CommandLineParser parser = new DefaultParser();
try {
val cmd = parser.parse(cliOptions, args);
return cmd.getOptionValue("config");
} catch (ParseException e) {
val error = String.format("%s. Use -c, --config <arg>", e.getMessage());
throw new RunnerException(error);
}
CommandLine cmd = parser.parse(cliOptions, args);
return cmd.getOptionValue("config", getDefaultConfigPath());
}
private static String getDefaultConfigPath() {
return Paths.get(System.getProperty("user.dir"), DEFAULT_CONFIG_FILENAME).toString();
}
static KeyStoreOptions createKeyStoreOptions(final Properties properties) {
@ -89,7 +86,7 @@ class Config {
static String getProperty(final Properties properties, final String key) {
final String value = properties.getProperty(key);
if (StringUtils.isBlank(value)) {
throw new InvalidParameterException(String.format("Missing value for config property: %s", key));
throw new IllegalArgumentException(String.format("Missing value for config property: %s", key));
}
return value;
}

View File

@ -6,13 +6,14 @@ import io.spiffe.helper.exception.RunnerException;
import io.spiffe.helper.keystore.KeyStoreHelper;
import lombok.extern.java.Log;
import lombok.val;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.nio.file.Paths;
import java.security.InvalidParameterException;
import java.security.KeyStoreException;
/**
* Entry point of the CLI to run the KeyStoreHelper.
* Entry point of the java-spiffe-helper CLI application.
*/
@Log
public class Runner {
@ -20,15 +21,19 @@ public class Runner {
private Runner() {
}
/**
* Entry method of the CLI to run the {@link KeyStoreHelper}.
* <p>
* In the args needs to be passed the config file option as: "-c" and "path_to_config_file"
*
* @param args contains the option with the config file path
* @throws RunnerException is there is an error configuring or creating the KeyStoreHelper.
*/
public static void main(final String ...args) throws RunnerException {
public static void main(final String... args) {
try {
runApplication(args);
} catch (RunnerException e) {
log.severe(ExceptionUtils.getStackTrace(e));
System.exit(1);
} catch (ParseException | IllegalArgumentException e) {
log.severe(e.getMessage());
System.exit(1);
}
}
static void runApplication(final String... args) throws RunnerException, ParseException {
try {
val configFilePath = Config.getCliConfigOption(args);
val properties = Config.parseConfigFileProperties(Paths.get(configFilePath));
@ -36,8 +41,7 @@ public class Runner {
try (val keyStoreHelper = KeyStoreHelper.create(options)) {
keyStoreHelper.run(true);
}
} catch (SocketEndpointAddressException | KeyStoreHelperException | RunnerException | InvalidParameterException | KeyStoreException e) {
log.severe(e.getMessage());
} catch (SocketEndpointAddressException | KeyStoreHelperException | KeyStoreException e) {
throw new RunnerException(e);
}
}

View File

@ -44,23 +44,29 @@ class KeyStore {
private java.security.KeyStore loadKeyStore() throws KeyStoreException {
try {
val keyStore = java.security.KeyStore.getInstance(keyStoreType.value());
// Initialize KeyStore
if (Files.exists(keyStoreFilePath)) {
try (final InputStream inputStream = Files.newInputStream(keyStoreFilePath)) {
keyStore.load(inputStream, keyStorePassword.toCharArray());
}
} else {
//create new keyStore
keyStore.load(null, keyStorePassword.toCharArray());
}
return keyStore;
return loadKeyStoreFromFile();
} catch (IOException | NoSuchAlgorithmException | CertificateException e) {
throw new KeyStoreException("KeyStore cannot be created", e);
}
}
private java.security.KeyStore loadKeyStoreFromFile() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
val keyStore = java.security.KeyStore.getInstance(keyStoreType.value());
// Initialize KeyStore
if (Files.exists(keyStoreFilePath)) {
try (final InputStream inputStream = Files.newInputStream(keyStoreFilePath)) {
keyStore.load(inputStream, keyStorePassword.toCharArray());
} catch (IOException e) {
throw new KeyStoreException("KeyStore cannot be opened", e);
}
} else {
// Create a new KeyStore if it doesn't exist
keyStore.load(null, keyStorePassword.toCharArray());
}
return keyStore;
}
/**
* Store a private key and X.509 certificate chain in a Java KeyStore

View File

@ -4,6 +4,7 @@ import io.spiffe.helper.exception.RunnerException;
import io.spiffe.helper.keystore.KeyStoreHelper;
import io.spiffe.helper.keystore.KeyStoreType;
import lombok.val;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Test;
@ -12,8 +13,7 @@ import java.nio.file.Paths;
import java.util.Properties;
import static io.spiffe.utils.TestUtils.toUri;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.*;
class ConfigTest {
@ -56,7 +56,7 @@ class ConfigTest {
try {
String option = Config.getCliConfigOption("-c", "test");
assertEquals("test", option);
} catch (RunnerException e) {
} catch (ParseException e) {
fail();
}
}
@ -66,27 +66,28 @@ class ConfigTest {
try {
String option = Config.getCliConfigOption("--config", "example");
assertEquals("example", option);
} catch (RunnerException e) {
} catch (ParseException e) {
fail();
}
}
@Test
void getCliConfigOption_unknownOption() {
try {
String option = Config.getCliConfigOption("-a", "test");
} catch (RunnerException e) {
assertEquals("Unrecognized option: -a. Use -c, --config <arg>", e.getMessage());
}
}
@Test
void testGetCliConfigOption_unknownLongOption() {
try {
Config.getCliConfigOption("--unknown", "example");
fail("expected parse exception");
} catch (RunnerException e) {
assertEquals("Unrecognized option: --unknown. Use -c, --config <arg>", e.getMessage());
} catch (ParseException e) {
assertTrue(e.getMessage().startsWith("Unrecognized option: --unknown"));
}
}
@Test
void getCliConfigOption_unknownOption() {
try {
String option = Config.getCliConfigOption("-a", "test");
fail("expected parse exception");
} catch (ParseException e) {
assertTrue(e.getMessage().startsWith("Unrecognized option: -a"));
}
}

View File

@ -2,6 +2,7 @@ package io.spiffe.helper.cli;
import io.spiffe.helper.exception.RunnerException;
import lombok.val;
import org.apache.commons.cli.ParseException;
import org.junit.jupiter.api.Test;
import java.io.File;
@ -17,70 +18,70 @@ import static org.junit.jupiter.api.Assertions.fail;
class RunnerTest {
@Test
void test_Main_KeyStorePathIsMissing() throws URISyntaxException {
void test_Main_KeyStorePathIsMissing() throws URISyntaxException, RunnerException, ParseException {
final Path path = Paths.get(toUri("testdata/cli/missing-keystorepath.conf"));
try {
Runner.main("-c", path.toString());
Runner.runApplication("-c", path.toString());
fail("expected exception: property is missing");
} catch (RunnerException e) {
assertEquals("Missing value for config property: keyStorePath", e.getCause().getMessage());
} catch (IllegalArgumentException e) {
assertEquals("Missing value for config property: keyStorePath", e.getMessage());
}
}
@Test
void test_Main_KeyStorePassIsMissing() throws URISyntaxException {
void test_Main_KeyStorePassIsMissing() throws URISyntaxException, RunnerException, ParseException {
final Path path = Paths.get(toUri("testdata/cli/missing-keystorepass.conf"));
try {
Runner.main("-c", path.toString());
Runner.runApplication("-c", path.toString());
fail("expected exception: property is missing");
} catch (RunnerException e) {
assertEquals("Missing value for config property: keyStorePass", e.getCause().getMessage());
} catch (IllegalArgumentException e) {
assertEquals("Missing value for config property: keyStorePass", e.getMessage());
}
}
@Test
void test_Main_KeyPassIsMissing() throws URISyntaxException {
void test_Main_KeyPassIsMissing() throws URISyntaxException, RunnerException, ParseException {
final Path path = Paths.get(toUri("testdata/cli/missing-keypass.conf"));
try {
Runner.main("-c", path.toString());
Runner.runApplication("-c", path.toString());
fail("expected exception: property is missing");
} catch (RunnerException e) {
assertEquals("Missing value for config property: keyPass", e.getCause().getMessage());
} catch (IllegalArgumentException e) {
assertEquals("Missing value for config property: keyPass", e.getMessage());
}
}
@Test
void test_Main_TrustStorePathIsMissing() throws URISyntaxException {
void test_Main_TrustStorePathIsMissing() throws URISyntaxException, RunnerException, ParseException {
final Path path = Paths.get(toUri("testdata/cli/missing-truststorepath.conf"));
try {
Runner.main("-c", path.toString());
Runner.runApplication("-c", path.toString());
fail("expected exception: property is missing");
} catch (RunnerException e) {
assertEquals("Missing value for config property: trustStorePath", e.getCause().getMessage());
} catch (IllegalArgumentException e) {
assertEquals("Missing value for config property: trustStorePath", e.getMessage());
}
}
@Test
void test_Main_TrustStorePassIsMissing() throws URISyntaxException {
void test_Main_TrustStorePassIsMissing() throws URISyntaxException, RunnerException, ParseException {
final Path path = Paths.get(toUri("testdata/cli/missing-truststorepass.conf"));
try {
Runner.main("-c", path.toString());
Runner.runApplication("-c", path.toString());
fail("expected exception: property is missing");
} catch (RunnerException e) {
assertEquals("Missing value for config property: trustStorePass", e.getCause().getMessage());
} catch (IllegalArgumentException e) {
assertEquals("Missing value for config property: trustStorePass", e.getMessage());
}
}
@Test
void test_Main_throwsExceptionIfTheKeystoreCannotBeCreated() throws URISyntaxException, IOException {
void test_Main_throwsExceptionIfTheKeystoreCannotBeCreated() throws URISyntaxException, IOException, ParseException {
val file = new File("keystore123.p12");
file.createNewFile();
val configPath = Paths.get(toUri("testdata/cli/correct.conf"));
try {
Runner.main("-c", configPath.toString());
Runner.runApplication("-c", configPath.toString());
} catch (RunnerException e) {
assertEquals("KeyStore cannot be created", e.getCause().getMessage());
assertEquals("KeyStore cannot be opened", e.getCause().getMessage());
} finally {
file.delete();
}

View File

@ -13,6 +13,7 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
import java.io.IOException;
import java.nio.file.Files;
@ -190,13 +191,14 @@ class KeyStoreHelperTest {
@Test
void testCreateKeyStoreHelper_createNewClient() throws Exception {
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/test");
val options = getKeyStoreValidOptions(null);
try {
KeyStoreHelper.create(options);
} catch (KeyStoreHelperException e) {
fail();
}
new EnvironmentVariables(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/test").execute(() -> {
val options = getKeyStoreValidOptions(null);
try {
KeyStoreHelper.create(options);
} catch (KeyStoreHelperException e) {
fail();
}
});
}
@Test

View File

@ -48,7 +48,7 @@ public class KeyStoreTest {
Paths.get(toUri("testdata/svid.key")));
x509Bundle = X509Bundle.load(
TrustDomain.of("spiffe://example.org"),
TrustDomain.parse("spiffe://example.org"),
Paths.get(toUri("testdata/bundle.pem")));
}
@ -203,7 +203,7 @@ public class KeyStoreTest {
.keyStorePassword("example")
.build();
} catch (KeyStoreException e) {
assertEquals("KeyStore cannot be created", e.getMessage());
assertEquals("KeyStore cannot be opened", e.getMessage());
} finally {
file.delete();
}

View File

@ -1,8 +1,10 @@
package io.spiffe.helper.keystore;
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
import io.spiffe.bundle.x509bundle.X509BundleSet;
import io.spiffe.exception.JwtBundleException;
import io.spiffe.exception.JwtSvidException;
import io.spiffe.exception.X509BundleException;
import io.spiffe.exception.X509ContextException;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.svid.jwtsvid.JwtSvid;
@ -11,6 +13,7 @@ import io.spiffe.workloadapi.WorkloadApiClient;
import io.spiffe.workloadapi.X509Context;
import lombok.NonNull;
import java.util.List;
import java.io.IOException;
public class WorkloadApiClientErrorStub implements WorkloadApiClient {
@ -25,6 +28,16 @@ public class WorkloadApiClientErrorStub implements WorkloadApiClient {
watcher.onError(new X509ContextException("Testing exception"));
}
@Override
public X509BundleSet fetchX509Bundles() throws X509BundleException {
throw new X509BundleException("Testing exception");
}
@Override
public void watchX509Bundles(@NonNull Watcher<X509BundleSet> watcher) {
watcher.onError(new X509BundleException("Testing exception"));
}
@Override
public JwtSvid fetchJwtSvid(@NonNull final String audience, final String... extraAudience) throws JwtSvidException {
throw new JwtSvidException("Testing exception");
@ -34,7 +47,15 @@ public class WorkloadApiClientErrorStub implements WorkloadApiClient {
public JwtSvid fetchJwtSvid(@NonNull final SpiffeId subject, @NonNull final String audience, final String... extraAudience) throws JwtSvidException {
throw new JwtSvidException("Testing exception");
}
@Override
public List<JwtSvid> fetchJwtSvids(@NonNull final String audience, final String... extraAudience) throws JwtSvidException {
throw new JwtSvidException("Testing exception");
}
@Override
public List<JwtSvid> fetchJwtSvids(@NonNull final SpiffeId subject, @NonNull final String audience, final String... extraAudience) throws JwtSvidException {
throw new JwtSvidException("Testing exception");
}
@Override
public JwtBundleSet fetchJwtBundles() throws JwtBundleException {
throw new JwtBundleException("Testing exception");

View File

@ -23,6 +23,8 @@ import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Collections;
public class WorkloadApiClientStub implements WorkloadApiClient {
@ -42,6 +44,17 @@ public class WorkloadApiClientStub implements WorkloadApiClient {
watcher.onUpdate(update);
}
@Override
public X509BundleSet fetchX509Bundles() throws X509BundleException {
return getX509BundleSet();
}
@Override
public void watchX509Bundles(@NonNull Watcher<X509BundleSet> watcher) {
val update = getX509BundleSet();
watcher.onUpdate(update);
}
@Override
public JwtSvid fetchJwtSvid(@NonNull String audience, String... extraAudience) throws JwtSvidException {
return null;
@ -52,6 +65,15 @@ public class WorkloadApiClientStub implements WorkloadApiClient {
return null;
}
@Override
public List<JwtSvid> fetchJwtSvids(@NonNull String audience, String... extraAudience) throws JwtSvidException {
return null;
}
@Override
public List<JwtSvid> fetchJwtSvids(@NonNull final SpiffeId subject, @NonNull final String audience, final String... extraAudience) throws JwtSvidException {
return null;
}
@Override
public JwtBundleSet fetchJwtBundles() throws JwtBundleException {
return null;
@ -82,7 +104,19 @@ public class WorkloadApiClientStub implements WorkloadApiClient {
try {
Path pathBundle = Paths.get(toUri(x509Bundle));
byte[] bundleBytes = Files.readAllBytes(pathBundle);
return X509Bundle.parse(TrustDomain.of("example.org"), bundleBytes);
return X509Bundle.parse(TrustDomain.parse("example.org"), bundleBytes);
} catch (IOException | X509BundleException e) {
throw new RuntimeException(e);
}
}
private X509BundleSet getX509BundleSet() {
try {
Path pathBundle = Paths.get(toUri(x509Bundle));
byte[] bundleBytes = Files.readAllBytes(pathBundle);
val bundle1 = X509Bundle.parse(TrustDomain.parse("example.org"), bundleBytes);
val bundle2 = X509Bundle.parse(TrustDomain.parse("domain.test"), bundleBytes);
return X509BundleSet.of(Arrays.asList(bundle1, bundle2));
} catch (IOException | X509BundleException e) {
throw new RuntimeException(e);
}

View File

@ -1,14 +1,12 @@
# Java SPIFFE Provider
This module provides a Java Security Provider implementation supporting X.509-SVIDs and methods for
creating SSLContexts that are backed by the Workload API.
creating `SSLContext` that are backed by the Workload API.
## Create an SSL Context backed by the Workload API
To create an SSL Context that uses a `X509Source` backed by the Workload API, having the environment variable
` SPIFFE_ENDPOINT_SOCKET` defined with the Workload API endpoint address.
The `SSLContext` is configured with a set of SPIFFE IDs that the current workload
will trust for TLS connections:
To create an `javax.net.ssl.SSLContext` that is backed by the Workload API through a `X509Source`, having the environment variable
` SPIFFE_ENDPOINT_SOCKET` defined with the Workload API endpoint address:
```
X509Source source = DefaultX509Source.newSource();
@ -20,9 +18,12 @@ will trust for TLS connections:
.build();
SSLContext sslContext = SpiffeSslContextFactory.getSslContext(options);
```
```
The `SSLContext` is configured with a set of SPIFFE IDs that will be trusted for TLS connections.
Alternatively, a different Workload API address can be used by passing it to the X509Source creation method.
Alternatively, a different Workload API address can be used by passing it to the `X509Source` creation method.
```
X509SourceOptions sourceOptions = X509SourceOptions
@ -136,6 +137,36 @@ export SPIFFE_ENDPOINT_SOCKET=/tmp/agent.sock
## Use Cases
### Connect to Postgres DB using TLS and the SPIFFE SslSocketFactory
A Java app can connect to a Postgres DB using TLS and authenticate itself using certificates provided by SPIRE through
the SPIFFE Workload API. To enable this functionality, there's a custom `SSLSocketFactory` implementation that injects a
custom `SSLContext` that uses the SPIFFE `KeyStore` and a `TrustStore` implementations to obtain certificates and bundles
from a SPIRE Agent, keep them updated in memory, and provide them for TLS connections.
The URL to connect to Postgres using TLS and Java SPIFFE is as follows:
```
jdbc:postgresql://localhost:5432/postgres?sslmode=require&sslfactory=io.spiffe.provider.SpiffeSslSocketFactory&sslNegotiation=direct
```
The parameter `sslfactory` in the URL configures the Postgres JDBC driver to use the `SpiffeSslSocketFactory` which wraps
around an SSL Socket with the Java SPIFFE functionality. Additional parameter `sslNegotiation` is needed to instantiate
`SpiffeSslSocketFactory` correct.
The Workload API socket endpoint should be configured through the Environment variable `SPIFFE_ENDPOINT_SOCKET`.
During the connection to a Postgres DB, the server presents its certificate, which is validated using trust bundles
obtained from the SPIFFE Workload API.
To also validate that the SPIFFE ID presented in the server's certificate is one of a list of expected SPIFFE IDs,
the property `ssl.spiffe.accept` needs to be configured with the expected SPIFFE IDs separated by commas.
For example:
```
-Dssl.spiffe.accept=spiffe://domain.test/db-1,spiffe://domain.test/db-2'
```
If this property is not configured, any SPIFFE ID will be accepted in a TLS connection.
### Configure a Tomcat connector
***Prerequisite***: Having the SPIFFE Provider configured through the `java.security`.
@ -154,7 +185,7 @@ A Tomcat TLS connector that uses the `Spiffe` KeyStore can be configured as foll
### Create mTLS GRPC server and client
Prerequisite: Having the SPIFFE Provided configured through the `java.security`.
Prerequisite: Having the SPIFFE Provider configured through the `java.security`.
A `GRPC Server` using an SSL context backed by the Workload API:
@ -189,7 +220,7 @@ with a [X509Source instance](../java-spiffe-core/README.md#x509-source).
KeyManager keyManager = new SpiffeKeyManager(x509Source);
// TrustManager gets the X509Source and the supplier of the Set of accepted SPIFFE IDs.
TrustManager trustManager = new SpiffeTrustManager(x509Source, () -> SpiffeIdUtils.toSetOfSpiffeIds("spiffe://example.org/workload-client", ','));
TrustManager trustManager = new SpiffeTrustManager(x509Source, () -> SpiffeIdUtils.toSetOfSpiffeIds("spiffe://example.org/workload-client"));
SslContextBuilder sslContextBuilder =
SslContextBuilder
@ -212,7 +243,7 @@ the GRPC SSL context, analogous to the config for the Server:
KeyManager keyManager = new SpiffeKeyManager(x509Source);
Supplier<Set<SpiffeId>> acceptedSpiffeIds = () -> SpiffeIdUtils.toSetOfSpiffeIds("spiffe://example.org/workload-server", ',');
Supplier<Set<SpiffeId>> acceptedSpiffeIds = () -> SpiffeIdUtils.toSetOfSpiffeIds("spiffe://example.org/workload-server");
TrustManager trustManager = new SpiffeTrustManager(x509Source, acceptedSpiffeIds);
SslContextBuilder sslContextBuilder = SslContextBuilder

View File

@ -1,5 +1,5 @@
plugins {
id "com.github.johnrengelman.shadow" version "5.2.0"
id "com.github.johnrengelman.shadow" version "${shadowVersion}"
}
description = "Java Security Provider implementation supporting X.509-SVIDs and methods for " +
@ -10,6 +10,7 @@ apply plugin: 'com.github.johnrengelman.shadow'
assemble.dependsOn shadowJar
shadowJar {
mergeServiceFiles()
archiveClassifier = "all-".concat(osdetector.classifier)
}
@ -17,8 +18,15 @@ dependencies {
api(project(":java-spiffe-core"))
// runtimeOnly grpc-netty dependency module will be included in the shadowJar
if (gradle.ext.isMacOsX) {
runtimeOnly(project(':java-spiffe-core:grpc-netty-macos'))
if (osdetector.os.is('osx') ) {
project.ext.osArch = System.getProperty("os.arch")
if ("x86_64" == project.ext.osArch) {
runtimeOnly(project(':java-spiffe-core:grpc-netty-macos'))
} else if ("aarch64" == project.ext.osArch) {
runtimeOnly(project(':java-spiffe-core:grpc-netty-macos-aarch64'))
} else {
throw new GradleException("Architecture not supported: " + project.ext.osArch)
}
} else {
runtimeOnly(project(':java-spiffe-core:grpc-netty-linux'))
}

View File

@ -0,0 +1,25 @@
package io.spiffe.provider;
import io.spiffe.spiffeid.SpiffeId;
import lombok.NonNull;
import java.security.cert.X509Certificate;
import java.util.Set;
import java.util.function.Supplier;
public class AllowedIdSupplierSpiffeIdVerifier implements SpiffeIdVerifier {
private final Supplier<Set<SpiffeId>> allowedSpiffeIdsSupplier;
public AllowedIdSupplierSpiffeIdVerifier(@NonNull Supplier<Set<SpiffeId>> allowedSpiffeIdsSupplier) {
this.allowedSpiffeIdsSupplier = allowedSpiffeIdsSupplier;
}
@Override
public void verify(SpiffeId spiffeId, X509Certificate[] verifiedChain) throws SpiffeVerificationException {
Set<SpiffeId> allowedSpiffeIds = allowedSpiffeIdsSupplier.get();
if (!allowedSpiffeIds.contains(spiffeId)) {
throw new SpiffeVerificationException(String.format("SPIFFE ID %s in X.509 certificate is not accepted", spiffeId));
}
}
}

View File

@ -0,0 +1,17 @@
package io.spiffe.provider;
import io.spiffe.spiffeid.SpiffeId;
import java.security.cert.X509Certificate;
public interface SpiffeIdVerifier {
/**
* Verify that an X509-SVID is acceptable. This method receives the SPIFFE ID of the SVID and the certificate
* chain.
*
* @param spiffeId the SPIFFE ID of the SVID
* @param verifiedChain the certificate chain with the X509-SVID certificate back to an X.509 root for the trust domain.
* @throws SpiffeVerificationException if there was an error verifying the SPIFFE ID or it wasn't considered valid.
*/
public void verify(SpiffeId spiffeId, X509Certificate[] verifiedChain) throws SpiffeVerificationException;
}

View File

@ -1,23 +1,77 @@
package io.spiffe.provider;
import io.spiffe.exception.SocketEndpointAddressException;
import io.spiffe.exception.X509SourceException;
import io.spiffe.provider.SpiffeSslContextFactory.SslContextOptions;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.spiffeid.SpiffeIdUtils;
import io.spiffe.workloadapi.DefaultX509Source;
import io.spiffe.workloadapi.X509Source;
import lombok.extern.java.Log;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Set;
import java.util.function.Supplier;
import java.util.logging.Level;
import static io.spiffe.provider.SpiffeProviderConstants.SSL_SPIFFE_ACCEPT_PROPERTY;
/**
* Implementation of {@link SSLSocketFactory} that provides methods to create {@link javax.net.ssl.SSLSocket}
* backed by a SPIFFE SSLContext {@link SpiffeSslContextFactory}.
*/
@Log
public class SpiffeSslSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
/**
* Default Constructor.
*
* This SpiffeSslSocketFactory is backed by SPIFFE-aware SSLContext that obtains certificates
* from the SPIFFE Workload API, connecting to a socket configured through the environment variable
* 'SPIFFE_ENDPOINT_SOCKET'.
*
* The list of accepted SPIFFE IDs, that will be used to validate the SAN in a peer certificate,
* can be configured through the property 'ssl.spiffe.accept', separating the SPIFFE IDs using commas
* without spaces, e.g., '-Dssl.spiffe.accept=spiffe://domain.test/service,spiffe://example.org/app'
* If the property is not set, any SPIFFE ID will be accepted in a TLS connection.
*
* @throws NoSuchAlgorithmException if there is a problem creating the SSL context
* @throws KeyManagementException if there is a problem initializing the SSL context
* @throws X509SourceException if there is a problem creating the source of X.509 certificates
* @throws SocketEndpointAddressException if there is a problem connecting to the local SPIFFE socket
*
*/
public SpiffeSslSocketFactory() throws SocketEndpointAddressException, X509SourceException, NoSuchAlgorithmException, KeyManagementException {
log.log(Level.INFO, "Creating SpiffeSslSocketFactory");
SSLContext sslContext;
Supplier<Set<SpiffeId>> acceptedSpiffeIds;
SslContextOptions options;
X509Source x509source = DefaultX509Source.newSource();
String envProperty = EnvironmentUtils.getProperty(SSL_SPIFFE_ACCEPT_PROPERTY);
if (StringUtils.isNotBlank(envProperty)) {
acceptedSpiffeIds = () -> SpiffeIdUtils.toSetOfSpiffeIds(envProperty, ',');
options = SslContextOptions.builder().acceptedSpiffeIdsSupplier(acceptedSpiffeIds).x509Source(x509source).build();
} else {
options = SslContextOptions.builder().acceptAnySpiffeId().x509Source(x509source).build();
}
sslContext = SpiffeSslContextFactory.getSslContext(options);
delegate = sslContext.getSocketFactory();
}
/**
* Constructor.
*

View File

@ -3,6 +3,7 @@ package io.spiffe.provider;
import io.spiffe.bundle.BundleSource;
import io.spiffe.bundle.x509bundle.X509Bundle;
import io.spiffe.exception.BundleNotFoundException;
import io.spiffe.internal.CertificateUtils;
import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.svid.x509svid.X509SvidValidator;
import lombok.NonNull;
@ -13,7 +14,6 @@ import java.net.Socket;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.function.Supplier;
@ -26,9 +26,10 @@ import java.util.function.Supplier;
*/
public final class SpiffeTrustManager extends X509ExtendedTrustManager {
private static final SpiffeIdVerifier ALLOW_ANY_SPIFFE_ID_VERIFIER = (spiffeId, verifiedChain) -> {};
private final BundleSource<X509Bundle> x509BundleSource;
private final Supplier<Set<SpiffeId>> acceptedSpiffeIdsSupplier;
private final boolean acceptAnySpiffeId;
private final SpiffeIdVerifier spiffeIdVerifier;
/**
* Constructor.
@ -42,8 +43,23 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager {
public SpiffeTrustManager(@NonNull final BundleSource<X509Bundle> x509BundleSource,
@NonNull final Supplier<Set<SpiffeId>> acceptedSpiffeIdsSupplier) {
this.x509BundleSource = x509BundleSource;
this.acceptedSpiffeIdsSupplier = acceptedSpiffeIdsSupplier;
this.acceptAnySpiffeId = false;
this.spiffeIdVerifier = new AllowedIdSupplierSpiffeIdVerifier(acceptedSpiffeIdsSupplier);
}
/**
* Constructor.
* <p>
* Creates a {@link SpiffeTrustManager} with an X.509 bundle source used to provide the trusted bundles,
* and a {@link SpiffeIdVerifier} which will be called to determine if a {@link SpiffeId} should be accepted
* during peer SVID validation.
*
* @param x509BundleSource an implementation of a {@link BundleSource}
* @param spiffeIdVerifier a {@link SpiffeIdVerifier} that will be called to determine if a peer's SPIFFE ID is acceptable
*/
public SpiffeTrustManager(@NonNull final BundleSource<X509Bundle> x509BundleSource,
@NonNull final SpiffeIdVerifier spiffeIdVerifier) {
this.x509BundleSource = x509BundleSource;
this.spiffeIdVerifier = spiffeIdVerifier;
}
/**
@ -58,8 +74,7 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager {
*/
public SpiffeTrustManager(@NonNull final BundleSource<X509Bundle> x509BundleSource) {
this.x509BundleSource = x509BundleSource;
this.acceptedSpiffeIdsSupplier = Collections::emptySet;
this.acceptAnySpiffeId = true;
this.spiffeIdVerifier = ALLOW_ANY_SPIFFE_ID_VERIFIER;
}
/**
@ -142,8 +157,11 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager {
// Check that the SPIFFE ID in the peer's certificate is accepted and the chain can be validated with a
// root CA in the bundle source
private void validatePeerChain(final X509Certificate... chain) throws CertificateException {
if (!acceptAnySpiffeId) {
X509SvidValidator.verifySpiffeId(chain[0], acceptedSpiffeIdsSupplier);
SpiffeId spiffeId = CertificateUtils.getSpiffeId(chain[0]);
try {
spiffeIdVerifier.verify(spiffeId, chain);
} catch (SpiffeVerificationException e) {
throw new CertificateException(e.getMessage(), e);
}
try {

View File

@ -0,0 +1,11 @@
package io.spiffe.provider;
/**
* This class indicates there was a problem verifying a peer's SPIFFE ID. The message should be used to indicate what
* issue was encountered.
*/
public class SpiffeVerificationException extends Exception {
public SpiffeVerificationException(String message) {
super(message);
}
}

View File

@ -38,11 +38,6 @@ public class SpiffeKeyManagerTest {
void setup() throws Exception {
MockitoAnnotations.initMocks(this);
val rootCa = createRootCA("C = US, O = SPIFFE", "spiffe://domain.test");
val leaf = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://domain.test/workload", rootCa, false);
X509Svid svid = X509Svid.parseRaw(leaf.getCertificate().getEncoded(), leaf.getKeyPair().getPrivate().getEncoded());
x509Svid = X509Svid.load(
Paths.get(toUri("testdata/cert.pem")),
Paths.get(toUri("testdata/key.pem")));

Some files were not shown because too many files have changed in this diff Show More