Add e2e test for OCSP and new configmap setting
This commit is contained in:
parent
6e8c68d888
commit
d18fa90cfd
19 changed files with 1299 additions and 9 deletions
|
@ -45,6 +45,7 @@ The following table shows a configuration option's name, type, and the default v
|
||||||
|[disable-ipv6](#disable-ipv6)|bool|false|
|
|[disable-ipv6](#disable-ipv6)|bool|false|
|
||||||
|[disable-ipv6-dns](#disable-ipv6-dns)|bool|false|
|
|[disable-ipv6-dns](#disable-ipv6-dns)|bool|false|
|
||||||
|[enable-underscores-in-headers](#enable-underscores-in-headers)|bool|false|
|
|[enable-underscores-in-headers](#enable-underscores-in-headers)|bool|false|
|
||||||
|
|[enable-ocsp](#enable-ocsp)|bool|false|
|
||||||
|[ignore-invalid-headers](#ignore-invalid-headers)|bool|true|
|
|[ignore-invalid-headers](#ignore-invalid-headers)|bool|true|
|
||||||
|[retry-non-idempotent](#retry-non-idempotent)|bool|"false"|
|
|[retry-non-idempotent](#retry-non-idempotent)|bool|"false"|
|
||||||
|[error-log-level](#error-log-level)|string|"notice"|
|
|[error-log-level](#error-log-level)|string|"notice"|
|
||||||
|
@ -282,6 +283,11 @@ Disable IPV6 for nginx DNS resolver. _**default:**_ `false`; IPv6 resolving enab
|
||||||
|
|
||||||
Enables underscores in header names. _**default:**_ is disabled
|
Enables underscores in header names. _**default:**_ is disabled
|
||||||
|
|
||||||
|
## enable-ocsp
|
||||||
|
|
||||||
|
Enables [Online Certificate Status Protocol stapling](https://en.wikipedia.org/wiki/OCSP_stapling) (OCSP) support.
|
||||||
|
_**default:**_ is disabled
|
||||||
|
|
||||||
## ignore-invalid-headers
|
## ignore-invalid-headers
|
||||||
|
|
||||||
Set if header fields with invalid names should be ignored.
|
Set if header fields with invalid names should be ignored.
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -38,6 +38,7 @@ require (
|
||||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
||||||
github.com/yudai/pp v2.0.1+incompatible // indirect
|
github.com/yudai/pp v2.0.1+incompatible // indirect
|
||||||
github.com/zakjan/cert-chain-resolver v0.0.0-20180703112424-6076e1ded272
|
github.com/zakjan/cert-chain-resolver v0.0.0-20180703112424-6076e1ded272
|
||||||
|
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
|
||||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
|
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
|
||||||
google.golang.org/grpc v1.26.0
|
google.golang.org/grpc v1.26.0
|
||||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7
|
gopkg.in/fsnotify/fsnotify.v1 v1.4.7
|
||||||
|
|
7
go.sum
7
go.sum
|
@ -274,6 +274,7 @@ github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
@ -321,6 +322,7 @@ github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASu
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
@ -440,6 +442,7 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
|
||||||
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
@ -602,6 +605,7 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
|
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
|
||||||
|
@ -876,6 +880,7 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7 h1:ZUjXAXmrAyrmmCP
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
@ -883,6 +888,7 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk=
|
google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk=
|
||||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
@ -1000,6 +1006,7 @@ sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJ
|
||||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
|
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||||
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
|
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
|
||||||
|
|
|
@ -129,6 +129,10 @@ type Configuration struct {
|
||||||
// By default this is disabled
|
// By default this is disabled
|
||||||
EnableModsecurity bool `json:"enable-modsecurity"`
|
EnableModsecurity bool `json:"enable-modsecurity"`
|
||||||
|
|
||||||
|
// EnableOCSP enables the OCSP support in SSL connections
|
||||||
|
// By default this is disabled
|
||||||
|
EnableOCSP bool `json:"enable-ocsp"`
|
||||||
|
|
||||||
// EnableOWASPCoreRules enables the OWASP ModSecurity Core Rule Set (CRS)
|
// EnableOWASPCoreRules enables the OWASP ModSecurity Core Rule Set (CRS)
|
||||||
// By default this is disabled
|
// By default this is disabled
|
||||||
EnableOWASPCoreRules bool `json:"enable-owasp-modsecurity-crs"`
|
EnableOWASPCoreRules bool `json:"enable-owasp-modsecurity-crs"`
|
||||||
|
|
|
@ -94,8 +94,7 @@ http {
|
||||||
error("require failed: " .. tostring(res))
|
error("require failed: " .. tostring(res))
|
||||||
else
|
else
|
||||||
certificate = res
|
certificate = res
|
||||||
-- TODO: get this from the configmap
|
certificate.is_ocsp_stapling_enabled = {{ $cfg.EnableOCSP }}
|
||||||
certificate.is_ocsp_stapling_enabled = false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
ok, res = pcall(require, "plugins")
|
ok, res = pcall(require, "plugins")
|
||||||
|
|
3
test/e2e-image/.gitignore
vendored
3
test/e2e-image/.gitignore
vendored
|
@ -2,3 +2,6 @@ e2e.test
|
||||||
ginkgo
|
ginkgo
|
||||||
kubectl
|
kubectl
|
||||||
charts/*
|
charts/*
|
||||||
|
*.json
|
||||||
|
*.db
|
||||||
|
*.go
|
||||||
|
|
|
@ -17,12 +17,6 @@ RUN curl -sSL https://raw.githubusercontent.com/helm/helm/master/scripts/get-hel
|
||||||
COPY --from=BASE /go/bin/ginkgo /usr/local/bin/
|
COPY --from=BASE /go/bin/ginkgo /usr/local/bin/
|
||||||
COPY --from=BASE /usr/local/bin/kubectl /usr/local/bin/
|
COPY --from=BASE /usr/local/bin/kubectl /usr/local/bin/
|
||||||
|
|
||||||
COPY e2e.sh /e2e.sh
|
COPY . /
|
||||||
COPY namespace-overlays /namespace-overlays
|
|
||||||
|
|
||||||
COPY wait-for-nginx.sh /
|
|
||||||
COPY e2e.test /
|
|
||||||
|
|
||||||
COPY charts /charts
|
|
||||||
|
|
||||||
CMD [ "/e2e.sh" ]
|
CMD [ "/e2e.sh" ]
|
||||||
|
|
|
@ -24,6 +24,9 @@ endif
|
||||||
cp $(DIR)/../e2e/wait-for-nginx.sh .
|
cp $(DIR)/../e2e/wait-for-nginx.sh .
|
||||||
cp -R $(DIR)/../../charts .
|
cp -R $(DIR)/../../charts .
|
||||||
|
|
||||||
|
# TODO: avoid manual copy
|
||||||
|
cp -R $(DIR)/../../test/e2e/settings/ocsp/* .
|
||||||
|
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--load \
|
--load \
|
||||||
--progress plain \
|
--progress plain \
|
||||||
|
|
|
@ -40,6 +40,7 @@ import (
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/security"
|
_ "k8s.io/ingress-nginx/test/e2e/security"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/servicebackend"
|
_ "k8s.io/ingress-nginx/test/e2e/servicebackend"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/settings"
|
_ "k8s.io/ingress-nginx/test/e2e/settings"
|
||||||
|
_ "k8s.io/ingress-nginx/test/e2e/settings/ocsp"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/ssl"
|
_ "k8s.io/ingress-nginx/test/e2e/ssl"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/status"
|
_ "k8s.io/ingress-nginx/test/e2e/status"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/tcpudp"
|
_ "k8s.io/ingress-nginx/test/e2e/tcpudp"
|
||||||
|
|
|
@ -104,6 +104,7 @@ docker tag ${REGISTRY}/nginx-ingress-controller-${ARCH}:${TAG} ${REGISTRY}/nginx
|
||||||
# Preload images used in e2e tests
|
# Preload images used in e2e tests
|
||||||
docker pull openresty/openresty:1.15.8.2-alpine
|
docker pull openresty/openresty:1.15.8.2-alpine
|
||||||
docker pull moul/grpcbin
|
docker pull moul/grpcbin
|
||||||
|
docker pull cfssl/cfssl:1.3.2
|
||||||
|
|
||||||
echo "[dev-env] copying docker images to cluster..."
|
echo "[dev-env] copying docker images to cluster..."
|
||||||
export EXIT_CODE=-1
|
export EXIT_CODE=-1
|
||||||
|
@ -115,6 +116,7 @@ kind load docker-image --name="${KIND_CLUSTER_NAME}" openresty/openresty:1.15.8.
|
||||||
kind load docker-image --name="${KIND_CLUSTER_NAME}" ${REGISTRY}/httpbin:${TAG}
|
kind load docker-image --name="${KIND_CLUSTER_NAME}" ${REGISTRY}/httpbin:${TAG}
|
||||||
kind load docker-image --name="${KIND_CLUSTER_NAME}" ${REGISTRY}/echo:${TAG}
|
kind load docker-image --name="${KIND_CLUSTER_NAME}" ${REGISTRY}/echo:${TAG}
|
||||||
kind load docker-image --name="${KIND_CLUSTER_NAME}" moul/grpcbin
|
kind load docker-image --name="${KIND_CLUSTER_NAME}" moul/grpcbin
|
||||||
|
kind load docker-image --name="${KIND_CLUSTER_NAME}" cfssl/cfssl:1.3.2
|
||||||
" | parallel --joblog /tmp/log {} || EXIT_CODE=$?
|
" | parallel --joblog /tmp/log {} || EXIT_CODE=$?
|
||||||
if [ ${EXIT_CODE} -eq 0 ] || [ ${EXIT_CODE} -eq -1 ];
|
if [ ${EXIT_CODE} -eq 0 ] || [ ${EXIT_CODE} -eq -1 ];
|
||||||
then
|
then
|
||||||
|
|
14
test/e2e/settings/ocsp/ca_csr.json
Normal file
14
test/e2e/settings/ocsp/ca_csr.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"CN": "Root CA",
|
||||||
|
"key": {
|
||||||
|
"algo": "rsa",
|
||||||
|
"size": 2048
|
||||||
|
},
|
||||||
|
"names": [{
|
||||||
|
"C": "US",
|
||||||
|
"L": "SF",
|
||||||
|
"O": "kubernetes",
|
||||||
|
"OU": "ingress-nginx",
|
||||||
|
"ST": "CA"
|
||||||
|
}]
|
||||||
|
}
|
1
test/e2e/settings/ocsp/db-config.json
Normal file
1
test/e2e/settings/ocsp/db-config.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"driver":"sqlite3","data_source":"empty.db"}
|
BIN
test/e2e/settings/ocsp/empty.db
Normal file
BIN
test/e2e/settings/ocsp/empty.db
Normal file
Binary file not shown.
14
test/e2e/settings/ocsp/intermediate_ca_csr.json
Normal file
14
test/e2e/settings/ocsp/intermediate_ca_csr.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"CN": "Intermediate CA",
|
||||||
|
"key": {
|
||||||
|
"algo": "rsa",
|
||||||
|
"size": 2048
|
||||||
|
},
|
||||||
|
"names": [{
|
||||||
|
"C": "US",
|
||||||
|
"L": "SF",
|
||||||
|
"O": "kubernetes",
|
||||||
|
"OU": "ingress-nginx",
|
||||||
|
"ST": "CA"
|
||||||
|
}]
|
||||||
|
}
|
18
test/e2e/settings/ocsp/leaf_csr.json
Normal file
18
test/e2e/settings/ocsp/leaf_csr.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"CN": "example.com",
|
||||||
|
"hosts": [
|
||||||
|
"example.com",
|
||||||
|
"www.example.com"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"algo": "rsa",
|
||||||
|
"size": 2048
|
||||||
|
},
|
||||||
|
"names": [{
|
||||||
|
"C": "US",
|
||||||
|
"L": "SF",
|
||||||
|
"O": "kubernetes",
|
||||||
|
"OU": "ingress-nginx",
|
||||||
|
"ST": "CA"
|
||||||
|
}]
|
||||||
|
}
|
423
test/e2e/settings/ocsp/ocsp.go
Normal file
423
test/e2e/settings/ocsp/ocsp.go
Normal file
|
@ -0,0 +1,423 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ocsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/onsi/ginkgo"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = framework.DescribeSetting("OCSP", func() {
|
||||||
|
f := framework.NewDefaultFramework("ocsp")
|
||||||
|
|
||||||
|
ginkgo.BeforeEach(func() {
|
||||||
|
f.NewEchoDeployment()
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should enable OCSP and contain stapling information in the connection", func() {
|
||||||
|
host := "www.example.com"
|
||||||
|
|
||||||
|
f.UpdateNginxConfigMapData("enable-ocsp", "true")
|
||||||
|
|
||||||
|
err := prepareCertificates(f.Namespace)
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||||||
|
|
||||||
|
ing := framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, f.Namespace, framework.EchoService, 80, nil)
|
||||||
|
f.EnsureIngress(ing)
|
||||||
|
|
||||||
|
leafCert, err := ioutil.ReadFile("leaf.pem")
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||||||
|
|
||||||
|
leafKey, err := ioutil.ReadFile("leaf-key.pem")
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||||||
|
|
||||||
|
intermediateCa, err := ioutil.ReadFile("intermediate_ca.pem")
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||||||
|
|
||||||
|
var pemCertBuffer bytes.Buffer
|
||||||
|
pemCertBuffer.Write(leafCert)
|
||||||
|
pemCertBuffer.Write([]byte("\n"))
|
||||||
|
pemCertBuffer.Write(intermediateCa)
|
||||||
|
|
||||||
|
f.EnsureSecret(&corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: ing.Spec.TLS[0].SecretName,
|
||||||
|
Namespace: f.Namespace,
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
corev1.TLSCertKey: pemCertBuffer.Bytes(),
|
||||||
|
corev1.TLSPrivateKeyKey: leafKey,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
cfsslDB, err := ioutil.ReadFile("empty.db")
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||||||
|
|
||||||
|
cmap, err := f.EnsureConfigMap(&corev1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "ocspserve",
|
||||||
|
Namespace: f.Namespace,
|
||||||
|
},
|
||||||
|
BinaryData: map[string][]byte{
|
||||||
|
"empty.db": cfsslDB,
|
||||||
|
"db-config.json": []byte(`{"driver":"sqlite3","data_source":"/data/empty.db"}`),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||||||
|
assert.NotNil(ginkgo.GinkgoT(), cmap)
|
||||||
|
|
||||||
|
d, s := ocspserveDeployment(f.Namespace)
|
||||||
|
f.EnsureDeployment(d)
|
||||||
|
f.EnsureService(s)
|
||||||
|
|
||||||
|
err = framework.WaitForEndpoints(f.KubeClientSet, framework.DefaultTimeout, "ocspserve", f.Namespace, 1)
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err, "waiting for endpoints to become ready")
|
||||||
|
|
||||||
|
f.WaitForNginxConfiguration(func(cfg string) bool {
|
||||||
|
return strings.Contains(cfg, "certificate.is_ocsp_stapling_enabled = true")
|
||||||
|
})
|
||||||
|
|
||||||
|
f.WaitForNginxServer(host,
|
||||||
|
func(server string) bool {
|
||||||
|
return strings.Contains(server, fmt.Sprintf(`server_name %v`, host))
|
||||||
|
})
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{ServerName: host, InsecureSkipVerify: true}
|
||||||
|
resp := f.HTTPTestClientWithTLSConfig(tlsConfig).
|
||||||
|
GET("/").
|
||||||
|
WithURL(f.GetURL(framework.HTTPS)).
|
||||||
|
WithHeader("Host", host).
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusOK).
|
||||||
|
Raw()
|
||||||
|
|
||||||
|
// TODO: avoid second request
|
||||||
|
resp = f.HTTPTestClientWithTLSConfig(tlsConfig).
|
||||||
|
GET("/").
|
||||||
|
WithURL(f.GetURL(framework.HTTPS)).
|
||||||
|
WithHeader("Host", host).
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusOK).
|
||||||
|
Raw()
|
||||||
|
|
||||||
|
state := resp.TLS
|
||||||
|
assert.NotNil(ginkgo.GinkgoT(), state.OCSPResponse, "unexpected connection without OCSP response")
|
||||||
|
|
||||||
|
var issuerCertificate *x509.Certificate
|
||||||
|
var leafAuthorityKeyID string
|
||||||
|
for index, certificate := range state.PeerCertificates {
|
||||||
|
if index == 0 {
|
||||||
|
leafAuthorityKeyID = string(certificate.AuthorityKeyId)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if leafAuthorityKeyID == string(certificate.SubjectKeyId) {
|
||||||
|
issuerCertificate = certificate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := ocsp.ParseResponse(state.OCSPResponse, issuerCertificate)
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||||||
|
assert.Equal(ginkgo.GinkgoT(), ocsp.Good, response.Status)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const configTemplate = `
|
||||||
|
{
|
||||||
|
"signing": {
|
||||||
|
"default": {
|
||||||
|
"ocsp_url": "http://ocspserve.%v.svc.cluster.local",
|
||||||
|
"expiry": "219000h",
|
||||||
|
"usages": [
|
||||||
|
"signing",
|
||||||
|
"key encipherment",
|
||||||
|
"client auth"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"ocsp": {
|
||||||
|
"usages": ["digital signature", "ocsp signing"],
|
||||||
|
"expiry": "8760h"
|
||||||
|
},
|
||||||
|
"intermediate": {
|
||||||
|
"usages": ["cert sign", "crl sign"],
|
||||||
|
"expiry": "219000h",
|
||||||
|
"ca_constraint": {
|
||||||
|
"is_ca": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"usages": ["signing", "key encipherment", "server auth"],
|
||||||
|
"expiry": "8760h"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"usages": ["signing", "key encipherment", "client auth"],
|
||||||
|
"expiry": "8760h"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func prepareCertificates(namespace string) error {
|
||||||
|
config := fmt.Sprintf(configTemplate, namespace)
|
||||||
|
err := ioutil.WriteFile("cfssl_config.json", []byte(config), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating cfssl_config.json file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfssl := "cfssl"
|
||||||
|
cfssljson := "cfssljson"
|
||||||
|
if !commandExists(cfssl) {
|
||||||
|
ginkgo.By("downloading cfssl...")
|
||||||
|
cfssl, err = downloadBinary(cfssl)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("downloading cfssl: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !commandExists(cfssljson) {
|
||||||
|
ginkgo.By("downloading cfssljson...")
|
||||||
|
cfssljson, err = downloadBinary(cfssljson)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("downloading cfssljson: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commands := []string{
|
||||||
|
fmt.Sprintf("%v gencert -initca ca_csr.json | %v -bare ca", cfssl, cfssljson),
|
||||||
|
fmt.Sprintf("%v gencert -ca ca.pem -ca-key ca-key.pem -config=cfssl_config.json -profile=intermediate intermediate_ca_csr.json | %v -bare intermediate_ca", cfssl, cfssljson),
|
||||||
|
fmt.Sprintf("%v gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config=cfssl_config.json -profile=ocsp ocsp_csr.json | %v -bare ocsp", cfssl, cfssljson),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, command := range commands {
|
||||||
|
ginkgo.By(fmt.Sprintf("running %v", command))
|
||||||
|
out, err := exec.Command("bash", "-c", command).Output()
|
||||||
|
if err != nil {
|
||||||
|
framework.Logf("Command error: %v\n%v\n%v", command, err, out)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, canc := context.WithCancel(context.Background())
|
||||||
|
defer canc()
|
||||||
|
|
||||||
|
command := fmt.Sprintf("%v serve -db-config=db-config.json -ca-key=intermediate_ca-key.pem -ca=intermediate_ca.pem -config=cfssl_config.json -responder=ocsp.pem -responder-key=ocsp-key.pem", cfssl)
|
||||||
|
ginkgo.By(fmt.Sprintf("running %v", command))
|
||||||
|
serve := exec.CommandContext(ctx, "bash", "-c", command)
|
||||||
|
if err := serve.Start(); err != nil {
|
||||||
|
framework.Logf("Command start error: %v\n%v", command, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
command = fmt.Sprintf("%v gencert -remote=localhost -profile=server leaf_csr.json | %v -bare leaf", cfssl, cfssljson)
|
||||||
|
ginkgo.By(fmt.Sprintf("running %v", command))
|
||||||
|
out, err := exec.Command("bash", "-c", command).Output()
|
||||||
|
if err != nil {
|
||||||
|
framework.Logf("Command error: %v\n%v\n%v", command, err, out)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = serve.Process.Signal(syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
framework.Logf("Command error: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
command = fmt.Sprintf("%v ocsprefresh -ca intermediate_ca.pem -responder=ocsp.pem -responder-key=ocsp-key.pem -db-config=db-config.json", cfssl)
|
||||||
|
ginkgo.By(fmt.Sprintf("running %v", command))
|
||||||
|
out, err = exec.Command("bash", "-c", command).Output()
|
||||||
|
if err != nil {
|
||||||
|
framework.Logf("Command error: %v\n%v\n%v", command, err, out)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Example:
|
||||||
|
cfssl ocspserve -port=8080 -db-config=db-config.json
|
||||||
|
openssl ocsp -issuer intermediate_ca.pem -no_nonce -cert leaf.pem -CAfile ca.pem -text -url http://localhost:8080
|
||||||
|
*/
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandExists(name string) bool {
|
||||||
|
_, err := exec.Command(name, "version").Output()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBinary(filepath string, url string) error {
|
||||||
|
out, err := os.Create(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("unexpected error downloading CFSSL")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(out, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadBinary(name string) (string, error) {
|
||||||
|
f, err := ioutil.TempFile("", "fw")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
arch := runtime.GOARCH
|
||||||
|
goos := runtime.GOOS
|
||||||
|
cfsslURL := "https://github.com/cloudflare/cfssl/releases/download/v1.4.1/" + name + "_1.4.1_" + goos + "_" + arch
|
||||||
|
|
||||||
|
err = getBinary(f.Name(), cfsslURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chmod(f.Name(), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ocspserveDeployment(namespace string) (*appsv1.Deployment, *corev1.Service) {
|
||||||
|
name := "ocspserve"
|
||||||
|
return &appsv1.Deployment{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
Spec: appsv1.DeploymentSpec{
|
||||||
|
Replicas: framework.NewInt32(1),
|
||||||
|
Selector: &metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{
|
||||||
|
"app": name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"app": name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
TerminationGracePeriodSeconds: framework.NewInt64(0),
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: name,
|
||||||
|
Image: "cfssl/cfssl:1.3.2",
|
||||||
|
Command: []string{
|
||||||
|
"/bin/bash",
|
||||||
|
"-c",
|
||||||
|
"cfssl ocspserve -port=80 -address=0.0.0.0 -db-config=/data/db-config.json -loglevel=0",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ContainerPort{
|
||||||
|
{
|
||||||
|
Name: "http",
|
||||||
|
ContainerPort: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: name,
|
||||||
|
MountPath: "/data",
|
||||||
|
ReadOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Volumes: []corev1.Volume{
|
||||||
|
{
|
||||||
|
Name: name,
|
||||||
|
VolumeSource: corev1.VolumeSource{
|
||||||
|
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "http",
|
||||||
|
Port: 80,
|
||||||
|
TargetPort: intstr.FromInt(80),
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Selector: map[string]string{
|
||||||
|
"app": name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
14
test/e2e/settings/ocsp/ocsp_csr.json
Normal file
14
test/e2e/settings/ocsp/ocsp_csr.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"CN": "OCSP Responder",
|
||||||
|
"key": {
|
||||||
|
"algo": "rsa",
|
||||||
|
"size": 2048
|
||||||
|
},
|
||||||
|
"names": [{
|
||||||
|
"C": "US",
|
||||||
|
"L": "SF",
|
||||||
|
"O": "kubernetes",
|
||||||
|
"OU": "ingress-nginx",
|
||||||
|
"ST": "CA"
|
||||||
|
}]
|
||||||
|
}
|
784
vendor/golang.org/x/crypto/ocsp/ocsp.go
generated
vendored
Normal file
784
vendor/golang.org/x/crypto/ocsp/ocsp.go
generated
vendored
Normal file
|
@ -0,0 +1,784 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package ocsp parses OCSP responses as specified in RFC 2560. OCSP responses
|
||||||
|
// are signed messages attesting to the validity of a certificate for a small
|
||||||
|
// period of time. This is used to manage revocation for X.509 certificates.
|
||||||
|
package ocsp // import "golang.org/x/crypto/ocsp"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
_ "crypto/sha1"
|
||||||
|
_ "crypto/sha256"
|
||||||
|
_ "crypto/sha512"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1})
|
||||||
|
|
||||||
|
// ResponseStatus contains the result of an OCSP request. See
|
||||||
|
// https://tools.ietf.org/html/rfc6960#section-2.3
|
||||||
|
type ResponseStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Success ResponseStatus = 0
|
||||||
|
Malformed ResponseStatus = 1
|
||||||
|
InternalError ResponseStatus = 2
|
||||||
|
TryLater ResponseStatus = 3
|
||||||
|
// Status code four is unused in OCSP. See
|
||||||
|
// https://tools.ietf.org/html/rfc6960#section-4.2.1
|
||||||
|
SignatureRequired ResponseStatus = 5
|
||||||
|
Unauthorized ResponseStatus = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r ResponseStatus) String() string {
|
||||||
|
switch r {
|
||||||
|
case Success:
|
||||||
|
return "success"
|
||||||
|
case Malformed:
|
||||||
|
return "malformed"
|
||||||
|
case InternalError:
|
||||||
|
return "internal error"
|
||||||
|
case TryLater:
|
||||||
|
return "try later"
|
||||||
|
case SignatureRequired:
|
||||||
|
return "signature required"
|
||||||
|
case Unauthorized:
|
||||||
|
return "unauthorized"
|
||||||
|
default:
|
||||||
|
return "unknown OCSP status: " + strconv.Itoa(int(r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseError is an error that may be returned by ParseResponse to indicate
|
||||||
|
// that the response itself is an error, not just that it's indicating that a
|
||||||
|
// certificate is revoked, unknown, etc.
|
||||||
|
type ResponseError struct {
|
||||||
|
Status ResponseStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ResponseError) Error() string {
|
||||||
|
return "ocsp: error from server: " + r.Status.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are internal structures that reflect the ASN.1 structure of an OCSP
|
||||||
|
// response. See RFC 2560, section 4.2.
|
||||||
|
|
||||||
|
type certID struct {
|
||||||
|
HashAlgorithm pkix.AlgorithmIdentifier
|
||||||
|
NameHash []byte
|
||||||
|
IssuerKeyHash []byte
|
||||||
|
SerialNumber *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc2560#section-4.1.1
|
||||||
|
type ocspRequest struct {
|
||||||
|
TBSRequest tbsRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
type tbsRequest struct {
|
||||||
|
Version int `asn1:"explicit,tag:0,default:0,optional"`
|
||||||
|
RequestorName pkix.RDNSequence `asn1:"explicit,tag:1,optional"`
|
||||||
|
RequestList []request
|
||||||
|
}
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
Cert certID
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseASN1 struct {
|
||||||
|
Status asn1.Enumerated
|
||||||
|
Response responseBytes `asn1:"explicit,tag:0,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseBytes struct {
|
||||||
|
ResponseType asn1.ObjectIdentifier
|
||||||
|
Response []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type basicResponse struct {
|
||||||
|
TBSResponseData responseData
|
||||||
|
SignatureAlgorithm pkix.AlgorithmIdentifier
|
||||||
|
Signature asn1.BitString
|
||||||
|
Certificates []asn1.RawValue `asn1:"explicit,tag:0,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseData struct {
|
||||||
|
Raw asn1.RawContent
|
||||||
|
Version int `asn1:"optional,default:0,explicit,tag:0"`
|
||||||
|
RawResponderID asn1.RawValue
|
||||||
|
ProducedAt time.Time `asn1:"generalized"`
|
||||||
|
Responses []singleResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type singleResponse struct {
|
||||||
|
CertID certID
|
||||||
|
Good asn1.Flag `asn1:"tag:0,optional"`
|
||||||
|
Revoked revokedInfo `asn1:"tag:1,optional"`
|
||||||
|
Unknown asn1.Flag `asn1:"tag:2,optional"`
|
||||||
|
ThisUpdate time.Time `asn1:"generalized"`
|
||||||
|
NextUpdate time.Time `asn1:"generalized,explicit,tag:0,optional"`
|
||||||
|
SingleExtensions []pkix.Extension `asn1:"explicit,tag:1,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type revokedInfo struct {
|
||||||
|
RevocationTime time.Time `asn1:"generalized"`
|
||||||
|
Reason asn1.Enumerated `asn1:"explicit,tag:0,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2}
|
||||||
|
oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4}
|
||||||
|
oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}
|
||||||
|
oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
|
||||||
|
oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
|
||||||
|
oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13}
|
||||||
|
oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3}
|
||||||
|
oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2}
|
||||||
|
oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1}
|
||||||
|
oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
|
||||||
|
oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
|
||||||
|
oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
|
||||||
|
)
|
||||||
|
|
||||||
|
var hashOIDs = map[crypto.Hash]asn1.ObjectIdentifier{
|
||||||
|
crypto.SHA1: asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}),
|
||||||
|
crypto.SHA256: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}),
|
||||||
|
crypto.SHA384: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2}),
|
||||||
|
crypto.SHA512: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below
|
||||||
|
var signatureAlgorithmDetails = []struct {
|
||||||
|
algo x509.SignatureAlgorithm
|
||||||
|
oid asn1.ObjectIdentifier
|
||||||
|
pubKeyAlgo x509.PublicKeyAlgorithm
|
||||||
|
hash crypto.Hash
|
||||||
|
}{
|
||||||
|
{x509.MD2WithRSA, oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */},
|
||||||
|
{x509.MD5WithRSA, oidSignatureMD5WithRSA, x509.RSA, crypto.MD5},
|
||||||
|
{x509.SHA1WithRSA, oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1},
|
||||||
|
{x509.SHA256WithRSA, oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256},
|
||||||
|
{x509.SHA384WithRSA, oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384},
|
||||||
|
{x509.SHA512WithRSA, oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512},
|
||||||
|
{x509.DSAWithSHA1, oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1},
|
||||||
|
{x509.DSAWithSHA256, oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256},
|
||||||
|
{x509.ECDSAWithSHA1, oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1},
|
||||||
|
{x509.ECDSAWithSHA256, oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256},
|
||||||
|
{x509.ECDSAWithSHA384, oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384},
|
||||||
|
{x509.ECDSAWithSHA512, oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below
|
||||||
|
func signingParamsForPublicKey(pub interface{}, requestedSigAlgo x509.SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error) {
|
||||||
|
var pubType x509.PublicKeyAlgorithm
|
||||||
|
|
||||||
|
switch pub := pub.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
pubType = x509.RSA
|
||||||
|
hashFunc = crypto.SHA256
|
||||||
|
sigAlgo.Algorithm = oidSignatureSHA256WithRSA
|
||||||
|
sigAlgo.Parameters = asn1.RawValue{
|
||||||
|
Tag: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
pubType = x509.ECDSA
|
||||||
|
|
||||||
|
switch pub.Curve {
|
||||||
|
case elliptic.P224(), elliptic.P256():
|
||||||
|
hashFunc = crypto.SHA256
|
||||||
|
sigAlgo.Algorithm = oidSignatureECDSAWithSHA256
|
||||||
|
case elliptic.P384():
|
||||||
|
hashFunc = crypto.SHA384
|
||||||
|
sigAlgo.Algorithm = oidSignatureECDSAWithSHA384
|
||||||
|
case elliptic.P521():
|
||||||
|
hashFunc = crypto.SHA512
|
||||||
|
sigAlgo.Algorithm = oidSignatureECDSAWithSHA512
|
||||||
|
default:
|
||||||
|
err = errors.New("x509: unknown elliptic curve")
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = errors.New("x509: only RSA and ECDSA keys supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestedSigAlgo == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, details := range signatureAlgorithmDetails {
|
||||||
|
if details.algo == requestedSigAlgo {
|
||||||
|
if details.pubKeyAlgo != pubType {
|
||||||
|
err = errors.New("x509: requested SignatureAlgorithm does not match private key type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sigAlgo.Algorithm, hashFunc = details.oid, details.hash
|
||||||
|
if hashFunc == 0 {
|
||||||
|
err = errors.New("x509: cannot sign with hash function requested")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
err = errors.New("x509: unknown SignatureAlgorithm")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(agl): this is taken from crypto/x509 and so should probably be exported
|
||||||
|
// from crypto/x509 or crypto/x509/pkix.
|
||||||
|
func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) x509.SignatureAlgorithm {
|
||||||
|
for _, details := range signatureAlgorithmDetails {
|
||||||
|
if oid.Equal(details.oid) {
|
||||||
|
return details.algo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x509.UnknownSignatureAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(rlb): This is not taken from crypto/x509, but it's of the same general form.
|
||||||
|
func getHashAlgorithmFromOID(target asn1.ObjectIdentifier) crypto.Hash {
|
||||||
|
for hash, oid := range hashOIDs {
|
||||||
|
if oid.Equal(target) {
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crypto.Hash(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOIDFromHashAlgorithm(target crypto.Hash) asn1.ObjectIdentifier {
|
||||||
|
for hash, oid := range hashOIDs {
|
||||||
|
if hash == target {
|
||||||
|
return oid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the exposed reflection of the internal OCSP structures.
|
||||||
|
|
||||||
|
// The status values that can be expressed in OCSP. See RFC 6960.
|
||||||
|
const (
|
||||||
|
// Good means that the certificate is valid.
|
||||||
|
Good = iota
|
||||||
|
// Revoked means that the certificate has been deliberately revoked.
|
||||||
|
Revoked
|
||||||
|
// Unknown means that the OCSP responder doesn't know about the certificate.
|
||||||
|
Unknown
|
||||||
|
// ServerFailed is unused and was never used (see
|
||||||
|
// https://go-review.googlesource.com/#/c/18944). ParseResponse will
|
||||||
|
// return a ResponseError when an error response is parsed.
|
||||||
|
ServerFailed
|
||||||
|
)
|
||||||
|
|
||||||
|
// The enumerated reasons for revoking a certificate. See RFC 5280.
|
||||||
|
const (
|
||||||
|
Unspecified = 0
|
||||||
|
KeyCompromise = 1
|
||||||
|
CACompromise = 2
|
||||||
|
AffiliationChanged = 3
|
||||||
|
Superseded = 4
|
||||||
|
CessationOfOperation = 5
|
||||||
|
CertificateHold = 6
|
||||||
|
|
||||||
|
RemoveFromCRL = 8
|
||||||
|
PrivilegeWithdrawn = 9
|
||||||
|
AACompromise = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request represents an OCSP request. See RFC 6960.
|
||||||
|
type Request struct {
|
||||||
|
HashAlgorithm crypto.Hash
|
||||||
|
IssuerNameHash []byte
|
||||||
|
IssuerKeyHash []byte
|
||||||
|
SerialNumber *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal marshals the OCSP request to ASN.1 DER encoded form.
|
||||||
|
func (req *Request) Marshal() ([]byte, error) {
|
||||||
|
hashAlg := getOIDFromHashAlgorithm(req.HashAlgorithm)
|
||||||
|
if hashAlg == nil {
|
||||||
|
return nil, errors.New("Unknown hash algorithm")
|
||||||
|
}
|
||||||
|
return asn1.Marshal(ocspRequest{
|
||||||
|
tbsRequest{
|
||||||
|
Version: 0,
|
||||||
|
RequestList: []request{
|
||||||
|
{
|
||||||
|
Cert: certID{
|
||||||
|
pkix.AlgorithmIdentifier{
|
||||||
|
Algorithm: hashAlg,
|
||||||
|
Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
|
||||||
|
},
|
||||||
|
req.IssuerNameHash,
|
||||||
|
req.IssuerKeyHash,
|
||||||
|
req.SerialNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response represents an OCSP response containing a single SingleResponse. See
|
||||||
|
// RFC 6960.
|
||||||
|
type Response struct {
|
||||||
|
// Status is one of {Good, Revoked, Unknown}
|
||||||
|
Status int
|
||||||
|
SerialNumber *big.Int
|
||||||
|
ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time
|
||||||
|
RevocationReason int
|
||||||
|
Certificate *x509.Certificate
|
||||||
|
// TBSResponseData contains the raw bytes of the signed response. If
|
||||||
|
// Certificate is nil then this can be used to verify Signature.
|
||||||
|
TBSResponseData []byte
|
||||||
|
Signature []byte
|
||||||
|
SignatureAlgorithm x509.SignatureAlgorithm
|
||||||
|
|
||||||
|
// IssuerHash is the hash used to compute the IssuerNameHash and IssuerKeyHash.
|
||||||
|
// Valid values are crypto.SHA1, crypto.SHA256, crypto.SHA384, and crypto.SHA512.
|
||||||
|
// If zero, the default is crypto.SHA1.
|
||||||
|
IssuerHash crypto.Hash
|
||||||
|
|
||||||
|
// RawResponderName optionally contains the DER-encoded subject of the
|
||||||
|
// responder certificate. Exactly one of RawResponderName and
|
||||||
|
// ResponderKeyHash is set.
|
||||||
|
RawResponderName []byte
|
||||||
|
// ResponderKeyHash optionally contains the SHA-1 hash of the
|
||||||
|
// responder's public key. Exactly one of RawResponderName and
|
||||||
|
// ResponderKeyHash is set.
|
||||||
|
ResponderKeyHash []byte
|
||||||
|
|
||||||
|
// Extensions contains raw X.509 extensions from the singleExtensions field
|
||||||
|
// of the OCSP response. When parsing certificates, this can be used to
|
||||||
|
// extract non-critical extensions that are not parsed by this package. When
|
||||||
|
// marshaling OCSP responses, the Extensions field is ignored, see
|
||||||
|
// ExtraExtensions.
|
||||||
|
Extensions []pkix.Extension
|
||||||
|
|
||||||
|
// ExtraExtensions contains extensions to be copied, raw, into any marshaled
|
||||||
|
// OCSP response (in the singleExtensions field). Values override any
|
||||||
|
// extensions that would otherwise be produced based on the other fields. The
|
||||||
|
// ExtraExtensions field is not populated when parsing certificates, see
|
||||||
|
// Extensions.
|
||||||
|
ExtraExtensions []pkix.Extension
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are pre-serialized error responses for the various non-success codes
|
||||||
|
// defined by OCSP. The Unauthorized code in particular can be used by an OCSP
|
||||||
|
// responder that supports only pre-signed responses as a response to requests
|
||||||
|
// for certificates with unknown status. See RFC 5019.
|
||||||
|
var (
|
||||||
|
MalformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01}
|
||||||
|
InternalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02}
|
||||||
|
TryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03}
|
||||||
|
SigRequredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05}
|
||||||
|
UnauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06}
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckSignatureFrom checks that the signature in resp is a valid signature
|
||||||
|
// from issuer. This should only be used if resp.Certificate is nil. Otherwise,
|
||||||
|
// the OCSP response contained an intermediate certificate that created the
|
||||||
|
// signature. That signature is checked by ParseResponse and only
|
||||||
|
// resp.Certificate remains to be validated.
|
||||||
|
func (resp *Response) CheckSignatureFrom(issuer *x509.Certificate) error {
|
||||||
|
return issuer.CheckSignature(resp.SignatureAlgorithm, resp.TBSResponseData, resp.Signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseError results from an invalid OCSP response.
|
||||||
|
type ParseError string
|
||||||
|
|
||||||
|
func (p ParseError) Error() string {
|
||||||
|
return string(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRequest parses an OCSP request in DER form. It only supports
|
||||||
|
// requests for a single certificate. Signed requests are not supported.
|
||||||
|
// If a request includes a signature, it will result in a ParseError.
|
||||||
|
func ParseRequest(bytes []byte) (*Request, error) {
|
||||||
|
var req ocspRequest
|
||||||
|
rest, err := asn1.Unmarshal(bytes, &req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(rest) > 0 {
|
||||||
|
return nil, ParseError("trailing data in OCSP request")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.TBSRequest.RequestList) == 0 {
|
||||||
|
return nil, ParseError("OCSP request contains no request body")
|
||||||
|
}
|
||||||
|
innerRequest := req.TBSRequest.RequestList[0]
|
||||||
|
|
||||||
|
hashFunc := getHashAlgorithmFromOID(innerRequest.Cert.HashAlgorithm.Algorithm)
|
||||||
|
if hashFunc == crypto.Hash(0) {
|
||||||
|
return nil, ParseError("OCSP request uses unknown hash function")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Request{
|
||||||
|
HashAlgorithm: hashFunc,
|
||||||
|
IssuerNameHash: innerRequest.Cert.NameHash,
|
||||||
|
IssuerKeyHash: innerRequest.Cert.IssuerKeyHash,
|
||||||
|
SerialNumber: innerRequest.Cert.SerialNumber,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseResponse parses an OCSP response in DER form. It only supports
|
||||||
|
// responses for a single certificate. If the response contains a certificate
|
||||||
|
// then the signature over the response is checked. If issuer is not nil then
|
||||||
|
// it will be used to validate the signature or embedded certificate.
|
||||||
|
//
|
||||||
|
// Invalid responses and parse failures will result in a ParseError.
|
||||||
|
// Error responses will result in a ResponseError.
|
||||||
|
func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) {
|
||||||
|
return ParseResponseForCert(bytes, nil, issuer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseResponseForCert parses an OCSP response in DER form and searches for a
|
||||||
|
// Response relating to cert. If such a Response is found and the OCSP response
|
||||||
|
// contains a certificate then the signature over the response is checked. If
|
||||||
|
// issuer is not nil then it will be used to validate the signature or embedded
|
||||||
|
// certificate.
|
||||||
|
//
|
||||||
|
// Invalid responses and parse failures will result in a ParseError.
|
||||||
|
// Error responses will result in a ResponseError.
|
||||||
|
func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Response, error) {
|
||||||
|
var resp responseASN1
|
||||||
|
rest, err := asn1.Unmarshal(bytes, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(rest) > 0 {
|
||||||
|
return nil, ParseError("trailing data in OCSP response")
|
||||||
|
}
|
||||||
|
|
||||||
|
if status := ResponseStatus(resp.Status); status != Success {
|
||||||
|
return nil, ResponseError{status}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !resp.Response.ResponseType.Equal(idPKIXOCSPBasic) {
|
||||||
|
return nil, ParseError("bad OCSP response type")
|
||||||
|
}
|
||||||
|
|
||||||
|
var basicResp basicResponse
|
||||||
|
rest, err = asn1.Unmarshal(resp.Response.Response, &basicResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(rest) > 0 {
|
||||||
|
return nil, ParseError("trailing data in OCSP response")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(basicResp.TBSResponseData.Responses); n == 0 || cert == nil && n > 1 {
|
||||||
|
return nil, ParseError("OCSP response contains bad number of responses")
|
||||||
|
}
|
||||||
|
|
||||||
|
var singleResp singleResponse
|
||||||
|
if cert == nil {
|
||||||
|
singleResp = basicResp.TBSResponseData.Responses[0]
|
||||||
|
} else {
|
||||||
|
match := false
|
||||||
|
for _, resp := range basicResp.TBSResponseData.Responses {
|
||||||
|
if cert.SerialNumber.Cmp(resp.CertID.SerialNumber) == 0 {
|
||||||
|
singleResp = resp
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
return nil, ParseError("no response matching the supplied certificate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := &Response{
|
||||||
|
TBSResponseData: basicResp.TBSResponseData.Raw,
|
||||||
|
Signature: basicResp.Signature.RightAlign(),
|
||||||
|
SignatureAlgorithm: getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm),
|
||||||
|
Extensions: singleResp.SingleExtensions,
|
||||||
|
SerialNumber: singleResp.CertID.SerialNumber,
|
||||||
|
ProducedAt: basicResp.TBSResponseData.ProducedAt,
|
||||||
|
ThisUpdate: singleResp.ThisUpdate,
|
||||||
|
NextUpdate: singleResp.NextUpdate,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the ResponderID CHOICE tag. ResponderID can be flattened into
|
||||||
|
// TBSResponseData once https://go-review.googlesource.com/34503 has been
|
||||||
|
// released.
|
||||||
|
rawResponderID := basicResp.TBSResponseData.RawResponderID
|
||||||
|
switch rawResponderID.Tag {
|
||||||
|
case 1: // Name
|
||||||
|
var rdn pkix.RDNSequence
|
||||||
|
if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &rdn); err != nil || len(rest) != 0 {
|
||||||
|
return nil, ParseError("invalid responder name")
|
||||||
|
}
|
||||||
|
ret.RawResponderName = rawResponderID.Bytes
|
||||||
|
case 2: // KeyHash
|
||||||
|
if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &ret.ResponderKeyHash); err != nil || len(rest) != 0 {
|
||||||
|
return nil, ParseError("invalid responder key hash")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, ParseError("invalid responder id tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(basicResp.Certificates) > 0 {
|
||||||
|
// Responders should only send a single certificate (if they
|
||||||
|
// send any) that connects the responder's certificate to the
|
||||||
|
// original issuer. We accept responses with multiple
|
||||||
|
// certificates due to a number responders sending them[1], but
|
||||||
|
// ignore all but the first.
|
||||||
|
//
|
||||||
|
// [1] https://github.com/golang/go/issues/21527
|
||||||
|
ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ret.CheckSignatureFrom(ret.Certificate); err != nil {
|
||||||
|
return nil, ParseError("bad signature on embedded certificate: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if issuer != nil {
|
||||||
|
if err := issuer.CheckSignature(ret.Certificate.SignatureAlgorithm, ret.Certificate.RawTBSCertificate, ret.Certificate.Signature); err != nil {
|
||||||
|
return nil, ParseError("bad OCSP signature: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if issuer != nil {
|
||||||
|
if err := ret.CheckSignatureFrom(issuer); err != nil {
|
||||||
|
return nil, ParseError("bad OCSP signature: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ext := range singleResp.SingleExtensions {
|
||||||
|
if ext.Critical {
|
||||||
|
return nil, ParseError("unsupported critical extension")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for h, oid := range hashOIDs {
|
||||||
|
if singleResp.CertID.HashAlgorithm.Algorithm.Equal(oid) {
|
||||||
|
ret.IssuerHash = h
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ret.IssuerHash == 0 {
|
||||||
|
return nil, ParseError("unsupported issuer hash algorithm")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case bool(singleResp.Good):
|
||||||
|
ret.Status = Good
|
||||||
|
case bool(singleResp.Unknown):
|
||||||
|
ret.Status = Unknown
|
||||||
|
default:
|
||||||
|
ret.Status = Revoked
|
||||||
|
ret.RevokedAt = singleResp.Revoked.RevocationTime
|
||||||
|
ret.RevocationReason = int(singleResp.Revoked.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestOptions contains options for constructing OCSP requests.
|
||||||
|
type RequestOptions struct {
|
||||||
|
// Hash contains the hash function that should be used when
|
||||||
|
// constructing the OCSP request. If zero, SHA-1 will be used.
|
||||||
|
Hash crypto.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts *RequestOptions) hash() crypto.Hash {
|
||||||
|
if opts == nil || opts.Hash == 0 {
|
||||||
|
// SHA-1 is nearly universally used in OCSP.
|
||||||
|
return crypto.SHA1
|
||||||
|
}
|
||||||
|
return opts.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRequest returns a DER-encoded, OCSP request for the status of cert. If
|
||||||
|
// opts is nil then sensible defaults are used.
|
||||||
|
func CreateRequest(cert, issuer *x509.Certificate, opts *RequestOptions) ([]byte, error) {
|
||||||
|
hashFunc := opts.hash()
|
||||||
|
|
||||||
|
// OCSP seems to be the only place where these raw hash identifiers are
|
||||||
|
// used. I took the following from
|
||||||
|
// http://msdn.microsoft.com/en-us/library/ff635603.aspx
|
||||||
|
_, ok := hashOIDs[hashFunc]
|
||||||
|
if !ok {
|
||||||
|
return nil, x509.ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hashFunc.Available() {
|
||||||
|
return nil, x509.ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
h := opts.hash().New()
|
||||||
|
|
||||||
|
var publicKeyInfo struct {
|
||||||
|
Algorithm pkix.AlgorithmIdentifier
|
||||||
|
PublicKey asn1.BitString
|
||||||
|
}
|
||||||
|
if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Write(publicKeyInfo.PublicKey.RightAlign())
|
||||||
|
issuerKeyHash := h.Sum(nil)
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
h.Write(issuer.RawSubject)
|
||||||
|
issuerNameHash := h.Sum(nil)
|
||||||
|
|
||||||
|
req := &Request{
|
||||||
|
HashAlgorithm: hashFunc,
|
||||||
|
IssuerNameHash: issuerNameHash,
|
||||||
|
IssuerKeyHash: issuerKeyHash,
|
||||||
|
SerialNumber: cert.SerialNumber,
|
||||||
|
}
|
||||||
|
return req.Marshal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResponse returns a DER-encoded OCSP response with the specified contents.
|
||||||
|
// The fields in the response are populated as follows:
|
||||||
|
//
|
||||||
|
// The responder cert is used to populate the responder's name field, and the
|
||||||
|
// certificate itself is provided alongside the OCSP response signature.
|
||||||
|
//
|
||||||
|
// The issuer cert is used to puplate the IssuerNameHash and IssuerKeyHash fields.
|
||||||
|
//
|
||||||
|
// The template is used to populate the SerialNumber, Status, RevokedAt,
|
||||||
|
// RevocationReason, ThisUpdate, and NextUpdate fields.
|
||||||
|
//
|
||||||
|
// If template.IssuerHash is not set, SHA1 will be used.
|
||||||
|
//
|
||||||
|
// The ProducedAt date is automatically set to the current date, to the nearest minute.
|
||||||
|
func CreateResponse(issuer, responderCert *x509.Certificate, template Response, priv crypto.Signer) ([]byte, error) {
|
||||||
|
var publicKeyInfo struct {
|
||||||
|
Algorithm pkix.AlgorithmIdentifier
|
||||||
|
PublicKey asn1.BitString
|
||||||
|
}
|
||||||
|
if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if template.IssuerHash == 0 {
|
||||||
|
template.IssuerHash = crypto.SHA1
|
||||||
|
}
|
||||||
|
hashOID := getOIDFromHashAlgorithm(template.IssuerHash)
|
||||||
|
if hashOID == nil {
|
||||||
|
return nil, errors.New("unsupported issuer hash algorithm")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !template.IssuerHash.Available() {
|
||||||
|
return nil, fmt.Errorf("issuer hash algorithm %v not linked into binary", template.IssuerHash)
|
||||||
|
}
|
||||||
|
h := template.IssuerHash.New()
|
||||||
|
h.Write(publicKeyInfo.PublicKey.RightAlign())
|
||||||
|
issuerKeyHash := h.Sum(nil)
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
h.Write(issuer.RawSubject)
|
||||||
|
issuerNameHash := h.Sum(nil)
|
||||||
|
|
||||||
|
innerResponse := singleResponse{
|
||||||
|
CertID: certID{
|
||||||
|
HashAlgorithm: pkix.AlgorithmIdentifier{
|
||||||
|
Algorithm: hashOID,
|
||||||
|
Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
|
||||||
|
},
|
||||||
|
NameHash: issuerNameHash,
|
||||||
|
IssuerKeyHash: issuerKeyHash,
|
||||||
|
SerialNumber: template.SerialNumber,
|
||||||
|
},
|
||||||
|
ThisUpdate: template.ThisUpdate.UTC(),
|
||||||
|
NextUpdate: template.NextUpdate.UTC(),
|
||||||
|
SingleExtensions: template.ExtraExtensions,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch template.Status {
|
||||||
|
case Good:
|
||||||
|
innerResponse.Good = true
|
||||||
|
case Unknown:
|
||||||
|
innerResponse.Unknown = true
|
||||||
|
case Revoked:
|
||||||
|
innerResponse.Revoked = revokedInfo{
|
||||||
|
RevocationTime: template.RevokedAt.UTC(),
|
||||||
|
Reason: asn1.Enumerated(template.RevocationReason),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rawResponderID := asn1.RawValue{
|
||||||
|
Class: 2, // context-specific
|
||||||
|
Tag: 1, // Name (explicit tag)
|
||||||
|
IsCompound: true,
|
||||||
|
Bytes: responderCert.RawSubject,
|
||||||
|
}
|
||||||
|
tbsResponseData := responseData{
|
||||||
|
Version: 0,
|
||||||
|
RawResponderID: rawResponderID,
|
||||||
|
ProducedAt: time.Now().Truncate(time.Minute).UTC(),
|
||||||
|
Responses: []singleResponse{innerResponse},
|
||||||
|
}
|
||||||
|
|
||||||
|
tbsResponseDataDER, err := asn1.Marshal(tbsResponseData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(priv.Public(), template.SignatureAlgorithm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responseHash := hashFunc.New()
|
||||||
|
responseHash.Write(tbsResponseDataDER)
|
||||||
|
signature, err := priv.Sign(rand.Reader, responseHash.Sum(nil), hashFunc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := basicResponse{
|
||||||
|
TBSResponseData: tbsResponseData,
|
||||||
|
SignatureAlgorithm: signatureAlgorithm,
|
||||||
|
Signature: asn1.BitString{
|
||||||
|
Bytes: signature,
|
||||||
|
BitLength: 8 * len(signature),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if template.Certificate != nil {
|
||||||
|
response.Certificates = []asn1.RawValue{
|
||||||
|
{FullBytes: template.Certificate.Raw},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseDER, err := asn1.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return asn1.Marshal(responseASN1{
|
||||||
|
Status: asn1.Enumerated(Success),
|
||||||
|
Response: responseBytes{
|
||||||
|
ResponseType: idPKIXOCSPBasic,
|
||||||
|
Response: responseDER,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
|
@ -303,6 +303,8 @@ github.com/yudai/golcs
|
||||||
## explicit
|
## explicit
|
||||||
github.com/zakjan/cert-chain-resolver/certUtil
|
github.com/zakjan/cert-chain-resolver/certUtil
|
||||||
# golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
|
# golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
|
||||||
|
## explicit
|
||||||
|
golang.org/x/crypto/ocsp
|
||||||
golang.org/x/crypto/ssh/terminal
|
golang.org/x/crypto/ssh/terminal
|
||||||
# golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
|
# golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
|
||||||
## explicit
|
## explicit
|
||||||
|
|
Loading…
Reference in a new issue