oci: add tests for insecure cosign support; refactor test utils

Add tests to test Cosign support for insecure registries. Furthermore,
refactor OCI test utils to be more user friendly and enable accurate
testing of HTTPS and HTTP OCI registries by circumnavigating Docker's
automatic connection downgrade for registries hosted on localhost.

Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
This commit is contained in:
Sanskar Jaiswal 2023-07-20 02:43:24 +05:30
parent 71f1080b41
commit fce7c10fc0
No known key found for this signature in database
GPG Key ID: 5982D0279C227FFD
11 changed files with 312 additions and 185 deletions

2
go.mod
View File

@ -41,6 +41,7 @@ require (
github.com/fluxcd/pkg/testserver v0.4.0 github.com/fluxcd/pkg/testserver v0.4.0
github.com/fluxcd/pkg/version v0.2.2 github.com/fluxcd/pkg/version v0.2.2
github.com/fluxcd/source-controller/api v1.0.0 github.com/fluxcd/source-controller/api v1.0.0
github.com/foxcpp/go-mockdns v1.0.0
github.com/go-git/go-billy/v5 v5.4.1 github.com/go-git/go-billy/v5 v5.4.1
github.com/go-git/go-git/v5 v5.8.1 github.com/go-git/go-git/v5 v5.8.1
github.com/go-logr/logr v1.2.4 github.com/go-logr/logr v1.2.4
@ -251,6 +252,7 @@ require (
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect github.com/minio/sha256-simd v1.0.1 // indirect

10
go.sum
View File

@ -418,6 +418,7 @@ github.com/fluxcd/pkg/testserver v0.4.0/go.mod h1:gjOKX41okmrGYOa4oOF2fiLedDAfPo
github.com/fluxcd/pkg/version v0.2.2 h1:ZpVXECeLA5hIQMft11iLp6gN3cKcz6UNuVTQPw/bRdI= github.com/fluxcd/pkg/version v0.2.2 h1:ZpVXECeLA5hIQMft11iLp6gN3cKcz6UNuVTQPw/bRdI=
github.com/fluxcd/pkg/version v0.2.2/go.mod h1:NGnh/no8S6PyfCDxRFrPY3T5BUnqP48MxfxNRU0z8C0= github.com/fluxcd/pkg/version v0.2.2/go.mod h1:NGnh/no8S6PyfCDxRFrPY3T5BUnqP48MxfxNRU0z8C0=
github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI=
github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
@ -862,7 +863,9 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
@ -1261,6 +1264,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -1341,6 +1345,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -1367,6 +1372,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
@ -1431,6 +1437,8 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1549,6 +1557,7 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -1591,6 +1600,7 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=

View File

@ -2285,8 +2285,12 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
workspaceDir := t.TempDir() workspaceDir := t.TempDir()
tt.registryOpts.disableDNSMocking = true
server, err := setupRegistryServer(ctx, workspaceDir, tt.registryOpts) server, err := setupRegistryServer(ctx, workspaceDir, tt.registryOpts)
g.Expect(err).NotTo(HaveOccurred()) g.Expect(err).NotTo(HaveOccurred())
t.Cleanup(func() {
server.Close()
})
// Load a test chart // Load a test chart
chartData, err := os.ReadFile(chartPath) chartData, err := os.ReadFile(chartPath)
@ -2395,8 +2399,13 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignature(t *testing.T
g := NewWithT(t) g := NewWithT(t)
tmpDir := t.TempDir() tmpDir := t.TempDir()
server, err := setupRegistryServer(ctx, tmpDir, registryOptions{}) server, err := setupRegistryServer(ctx, tmpDir, registryOptions{
disableDNSMocking: true,
})
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(func() {
server.Close()
})
const ( const (
chartPath = "testdata/charts/helmchart-0.1.0.tgz" chartPath = "testdata/charts/helmchart-0.1.0.tgz"

View File

@ -250,8 +250,12 @@ func TestHelmRepositoryOCIReconciler_authStrategy(t *testing.T) {
WithStatusSubresource(&helmv1.HelmRepository{}) WithStatusSubresource(&helmv1.HelmRepository{})
workspaceDir := t.TempDir() workspaceDir := t.TempDir()
tt.registryOpts.disableDNSMocking = true
server, err := setupRegistryServer(ctx, workspaceDir, tt.registryOpts) server, err := setupRegistryServer(ctx, workspaceDir, tt.registryOpts)
g.Expect(err).NotTo(HaveOccurred()) g.Expect(err).NotTo(HaveOccurred())
t.Cleanup(func() {
server.Close()
})
obj := &helmv1.HelmRepository{ obj := &helmv1.HelmRepository{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View File

@ -18,7 +18,6 @@ package controller
import ( import (
"crypto/rand" "crypto/rand"
"crypto/rsa"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
@ -26,9 +25,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"net"
"net/http" "net/http"
"net/http/httptest"
"net/url" "net/url"
"os" "os"
"path" "path"
@ -39,7 +36,6 @@ import (
"github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/crane" "github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/registry"
gcrv1 "github.com/google/go-containerregistry/pkg/v1" gcrv1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/mutate"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -80,8 +76,11 @@ func TestOCIRepository_Reconcile(t *testing.T) {
if err != nil { if err != nil {
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
} }
t.Cleanup(func() {
regServer.Close()
})
podinfoVersions, err := pushMultiplePodinfoImages(regServer.registryHost, "6.1.4", "6.1.5", "6.1.6") podinfoVersions, err := pushMultiplePodinfoImages(regServer.registryHost, true, "6.1.4", "6.1.5", "6.1.6")
tests := []struct { tests := []struct {
name string name string
@ -146,6 +145,7 @@ func TestOCIRepository_Reconcile(t *testing.T) {
URL: tt.url, URL: tt.url,
Interval: metav1.Duration{Duration: 60 * time.Minute}, Interval: metav1.Duration{Duration: 60 * time.Minute},
Reference: &ociv1.OCIRepositoryRef{}, Reference: &ociv1.OCIRepositoryRef{},
Insecure: true,
}, },
} }
obj := origObj.DeepCopy() obj := origObj.DeepCopy()
@ -262,8 +262,11 @@ func TestOCIRepository_Reconcile_MediaType(t *testing.T) {
if err != nil { if err != nil {
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
} }
t.Cleanup(func() {
regServer.Close()
})
podinfoVersions, err := pushMultiplePodinfoImages(regServer.registryHost, "6.1.4", "6.1.5", "6.1.6") podinfoVersions, err := pushMultiplePodinfoImages(regServer.registryHost, true, "6.1.4", "6.1.5", "6.1.6")
tests := []struct { tests := []struct {
name string name string
@ -314,6 +317,7 @@ func TestOCIRepository_Reconcile_MediaType(t *testing.T) {
LayerSelector: &ociv1.OCILayerSelector{ LayerSelector: &ociv1.OCILayerSelector{
MediaType: tt.mediaType, MediaType: tt.mediaType,
}, },
Insecure: true,
}, },
} }
@ -373,6 +377,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
craneOpts []crane.Option craneOpts []crane.Option
secretOpts secretOptions secretOpts secretOptions
tlsCertSecret *corev1.Secret tlsCertSecret *corev1.Secret
insecure bool
provider string provider string
providerImg string providerImg string
want sreconcile.Result want sreconcile.Result
@ -380,8 +385,10 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
assertConditions []metav1.Condition assertConditions []metav1.Condition
}{ }{
{ {
name: "HTTP without basic auth", name: "HTTP without basic auth",
want: sreconcile.ResultSuccess, want: sreconcile.ResultSuccess,
craneOpts: []crane.Option{crane.Insecure},
insecure: true,
assertConditions: []metav1.Condition{ assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"), *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"), *conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
@ -393,10 +400,13 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
registryOpts: registryOptions{ registryOpts: registryOptions{
withBasicAuth: true, withBasicAuth: true,
}, },
craneOpts: []crane.Option{crane.WithAuth(&authn.Basic{ insecure: true,
Username: testRegistryUsername, craneOpts: []crane.Option{
Password: testRegistryPassword, crane.WithAuth(&authn.Basic{
}), Username: testRegistryUsername,
Password: testRegistryPassword,
}),
crane.Insecure,
}, },
secretOpts: secretOptions{ secretOpts: secretOptions{
username: testRegistryUsername, username: testRegistryUsername,
@ -414,10 +424,13 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
registryOpts: registryOptions{ registryOpts: registryOptions{
withBasicAuth: true, withBasicAuth: true,
}, },
craneOpts: []crane.Option{crane.WithAuth(&authn.Basic{ insecure: true,
Username: testRegistryUsername, craneOpts: []crane.Option{
Password: testRegistryPassword, crane.WithAuth(&authn.Basic{
}), Username: testRegistryUsername,
Password: testRegistryPassword,
}),
crane.Insecure,
}, },
secretOpts: secretOptions{ secretOpts: secretOptions{
username: testRegistryUsername, username: testRegistryUsername,
@ -435,11 +448,14 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
registryOpts: registryOptions{ registryOpts: registryOptions{
withBasicAuth: true, withBasicAuth: true,
}, },
wantErr: true, insecure: true,
craneOpts: []crane.Option{crane.WithAuth(&authn.Basic{ wantErr: true,
Username: testRegistryUsername, craneOpts: []crane.Option{
Password: testRegistryPassword, crane.WithAuth(&authn.Basic{
}), Username: testRegistryUsername,
Password: testRegistryPassword,
}),
crane.Insecure,
}, },
assertConditions: []metav1.Condition{ assertConditions: []metav1.Condition{
*conditions.TrueCondition(sourcev1.FetchFailedCondition, ociv1.OCIPullFailedReason, "failed to determine artifact digest"), *conditions.TrueCondition(sourcev1.FetchFailedCondition, ociv1.OCIPullFailedReason, "failed to determine artifact digest"),
@ -452,10 +468,13 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
registryOpts: registryOptions{ registryOpts: registryOptions{
withBasicAuth: true, withBasicAuth: true,
}, },
craneOpts: []crane.Option{crane.WithAuth(&authn.Basic{ insecure: true,
Username: testRegistryUsername, craneOpts: []crane.Option{
Password: testRegistryPassword, crane.WithAuth(&authn.Basic{
}), Username: testRegistryUsername,
Password: testRegistryPassword,
}),
crane.Insecure,
}, },
secretOpts: secretOptions{ secretOpts: secretOptions{
username: "wrong-pass", username: "wrong-pass",
@ -467,16 +486,19 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
}, },
}, },
{ {
name: "HTTP registry - basic auth with invalid serviceaccount", name: "HTTP registry - basic auth with invalid serviceaccount",
want: sreconcile.ResultEmpty, want: sreconcile.ResultEmpty,
wantErr: true, wantErr: true,
insecure: true,
registryOpts: registryOptions{ registryOpts: registryOptions{
withBasicAuth: true, withBasicAuth: true,
}, },
craneOpts: []crane.Option{crane.WithAuth(&authn.Basic{ craneOpts: []crane.Option{
Username: testRegistryUsername, crane.WithAuth(&authn.Basic{
Password: testRegistryPassword, Username: testRegistryUsername,
}), Password: testRegistryPassword,
}),
crane.Insecure,
}, },
secretOpts: secretOptions{ secretOpts: secretOptions{
username: "wrong-pass", username: "wrong-pass",
@ -559,25 +581,32 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
wantErr: true, wantErr: true,
provider: "aws", provider: "aws",
providerImg: "oci://123456789000.dkr.ecr.us-east-2.amazonaws.com/test", providerImg: "oci://123456789000.dkr.ecr.us-east-2.amazonaws.com/test",
craneOpts: []crane.Option{
crane.Insecure,
},
assertConditions: []metav1.Condition{ assertConditions: []metav1.Condition{
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get credential from"), *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get credential from"),
}, },
}, },
{ {
name: "with contextual login provider and secretRef", name: "secretRef takes precedence over provider",
want: sreconcile.ResultSuccess, want: sreconcile.ResultSuccess,
registryOpts: registryOptions{ registryOpts: registryOptions{
withBasicAuth: true, withBasicAuth: true,
}, },
craneOpts: []crane.Option{crane.WithAuth(&authn.Basic{ craneOpts: []crane.Option{
Username: testRegistryUsername, crane.WithAuth(&authn.Basic{
Password: testRegistryPassword, Username: testRegistryUsername,
})}, Password: testRegistryPassword,
}),
crane.Insecure,
},
secretOpts: secretOptions{ secretOpts: secretOptions{
username: testRegistryUsername, username: testRegistryUsername,
password: testRegistryPassword, password: testRegistryPassword,
includeSecret: true, includeSecret: true,
}, },
insecure: true,
provider: "azure", provider: "azure",
assertConditions: []metav1.Condition{ assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"), *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
@ -607,8 +636,10 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
workspaceDir := t.TempDir() workspaceDir := t.TempDir()
server, err := setupRegistryServer(ctx, workspaceDir, tt.registryOpts) server, err := setupRegistryServer(ctx, workspaceDir, tt.registryOpts)
g.Expect(err).NotTo(HaveOccurred()) g.Expect(err).NotTo(HaveOccurred())
t.Cleanup(func() {
server.Close()
})
img, err := createPodinfoImageFromTar("podinfo-6.1.6.tar", "6.1.6", server.registryHost, tt.craneOpts...) img, err := createPodinfoImageFromTar("podinfo-6.1.6.tar", "6.1.6", server.registryHost, tt.craneOpts...)
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
@ -664,6 +695,9 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
Name: tt.tlsCertSecret.Name, Name: tt.tlsCertSecret.Name,
} }
} }
if tt.insecure {
obj.Spec.Insecure = true
}
r := &OCIRepositoryReconciler{ r := &OCIRepositoryReconciler{
Client: clientBuilder.Build(), Client: clientBuilder.Build(),
@ -672,7 +706,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"),
} }
opts := craneOptions(ctx, true) opts := craneOptions(ctx, tt.insecure)
opts = append(opts, crane.WithAuthFromKeychain(authn.DefaultKeychain)) opts = append(opts, crane.WithAuthFromKeychain(authn.DefaultKeychain))
repoURL, err := r.getArtifactURL(obj, opts) repoURL, err := r.getArtifactURL(obj, opts)
g.Expect(err).To(BeNil()) g.Expect(err).To(BeNil())
@ -706,34 +740,36 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
func TestOCIRepository_CertSecret(t *testing.T) { func TestOCIRepository_CertSecret(t *testing.T) {
g := NewWithT(t) g := NewWithT(t)
srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err := createTLSServer() tmpDir := t.TempDir()
regServer, err := setupRegistryServer(ctx, tmpDir, registryOptions{
withTLS: true,
withClientCertAuth: true,
})
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(func() {
regServer.Close()
})
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(tlsCA)
clientTLSCert, err := tls.X509KeyPair(clientPublicKey, clientPrivateKey)
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
srv.StartTLS() transport := http.DefaultTransport.(*http.Transport)
defer srv.Close() transport.TLSClientConfig = &tls.Config{
RootCAs: pool,
transport := &http.Transport{ Certificates: []tls.Certificate{clientTLSCert},
TLSClientConfig: &tls.Config{},
} }
// Use the server cert as a CA cert, so the client trusts the pi, err := createPodinfoImageFromTar("podinfo-6.1.5.tar", "6.1.5", regServer.registryHost, []crane.Option{
// server cert. (Only works because the server uses the same crane.WithTransport(transport),
// cert in both roles).
pool := x509.NewCertPool()
pool.AddCert(srv.Certificate())
transport.TLSClientConfig.RootCAs = pool
transport.TLSClientConfig.Certificates = []tls.Certificate{clientTLSCert}
srv.Client().Transport = transport
pi, err := createPodinfoImageFromTar("podinfo-6.1.5.tar", "6.1.5", srv.URL, []crane.Option{
crane.WithTransport(srv.Client().Transport),
}...) }...)
g.Expect(err).NotTo(HaveOccurred()) g.Expect(err).NotTo(HaveOccurred())
tlsSecretClientCert := corev1.Secret{ tlsSecretClientCert := corev1.Secret{
StringData: map[string]string{ Data: map[string][]byte{
oci.CACert: string(rootCertPEM), oci.CACert: tlsCA,
oci.ClientCert: string(clientCertPEM), oci.ClientCert: clientPublicKey,
oci.ClientKey: string(clientKeyPEM), oci.ClientKey: clientPrivateKey,
}, },
} }
@ -758,17 +794,17 @@ func TestOCIRepository_CertSecret(t *testing.T) {
url: pi.url, url: pi.url,
digest: pi.digest, digest: pi.digest,
expectreadyconition: false, expectreadyconition: false,
expectedstatusmessage: "unexpected status code 400 Bad Request: Client sent an HTTP request to an HTTPS server", expectedstatusmessage: "tls: failed to verify certificate: x509:",
}, },
{ {
name: "test connection with with incorrect private key", name: "test connection with with incorrect private key",
url: pi.url, url: pi.url,
digest: pi.digest, digest: pi.digest,
certSecret: &corev1.Secret{ certSecret: &corev1.Secret{
StringData: map[string]string{ Data: map[string][]byte{
oci.CACert: string(rootCertPEM), oci.CACert: tlsCA,
oci.ClientCert: string(clientCertPEM), oci.ClientCert: clientPublicKey,
oci.ClientKey: string("invalid-key"), oci.ClientKey: []byte("invalid-key"),
}, },
}, },
expectreadyconition: false, expectreadyconition: false,
@ -859,8 +895,11 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
server, err := setupRegistryServer(ctx, tmpDir, registryOptions{}) server, err := setupRegistryServer(ctx, tmpDir, registryOptions{})
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(func() {
server.Close()
})
podinfoVersions, err := pushMultiplePodinfoImages(server.registryHost, "6.1.4", "6.1.5", "6.1.6") podinfoVersions, err := pushMultiplePodinfoImages(server.registryHost, true, "6.1.4", "6.1.5", "6.1.6")
img6 := podinfoVersions["6.1.6"] img6 := podinfoVersions["6.1.6"]
img5 := podinfoVersions["6.1.5"] img5 := podinfoVersions["6.1.5"]
@ -1001,6 +1040,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
URL: fmt.Sprintf("oci://%s/podinfo", server.registryHost), URL: fmt.Sprintf("oci://%s/podinfo", server.registryHost),
Interval: metav1.Duration{Duration: interval}, Interval: metav1.Duration{Duration: interval},
Timeout: &metav1.Duration{Duration: timeout}, Timeout: &metav1.Duration{Duration: timeout},
Insecure: true,
}, },
} }
@ -1034,26 +1074,16 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) { func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
g := NewWithT(t) g := NewWithT(t)
tmpDir := t.TempDir()
server, err := setupRegistryServer(ctx, tmpDir, registryOptions{})
g.Expect(err).ToNot(HaveOccurred())
podinfoVersions, err := pushMultiplePodinfoImages(server.registryHost, "6.1.4", "6.1.5")
g.Expect(err).ToNot(HaveOccurred())
img4 := podinfoVersions["6.1.4"]
img5 := podinfoVersions["6.1.5"]
tests := []struct { tests := []struct {
name string name string
reference *ociv1.OCIRepositoryRef reference *ociv1.OCIRepositoryRef
insecure bool insecure bool
digest string
want sreconcile.Result want sreconcile.Result
wantErr bool wantErr bool
wantErrMsg string wantErrMsg string
shouldSign bool shouldSign bool
keyless bool keyless bool
beforeFunc func(obj *ociv1.OCIRepository) beforeFunc func(obj *ociv1.OCIRepository, tag, revision string)
assertConditions []metav1.Condition assertConditions []metav1.Condition
}{ }{
{ {
@ -1061,7 +1091,6 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
reference: &ociv1.OCIRepositoryRef{ reference: &ociv1.OCIRepositoryRef{
Tag: "6.1.4", Tag: "6.1.4",
}, },
digest: img4.digest.String(),
shouldSign: true, shouldSign: true,
want: sreconcile.ResultSuccess, want: sreconcile.ResultSuccess,
assertConditions: []metav1.Condition{ assertConditions: []metav1.Condition{
@ -1075,7 +1104,6 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
reference: &ociv1.OCIRepositoryRef{ reference: &ociv1.OCIRepositoryRef{
Tag: "6.1.5", Tag: "6.1.5",
}, },
digest: img5.digest.String(),
wantErr: true, wantErr: true,
wantErrMsg: "failed to verify the signature using provider 'cosign': no matching signatures were found for '<url>'", wantErrMsg: "failed to verify the signature using provider 'cosign': no matching signatures were found for '<url>'",
want: sreconcile.ResultEmpty, want: sreconcile.ResultEmpty,
@ -1090,7 +1118,6 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
reference: &ociv1.OCIRepositoryRef{ reference: &ociv1.OCIRepositoryRef{
Tag: "6.1.5", Tag: "6.1.5",
}, },
digest: img5.digest.String(),
wantErr: true, wantErr: true,
want: sreconcile.ResultEmpty, want: sreconcile.ResultEmpty,
keyless: true, keyless: true,
@ -1103,21 +1130,19 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
{ {
name: "verify failed before, removed from spec, remove condition", name: "verify failed before, removed from spec, remove condition",
reference: &ociv1.OCIRepositoryRef{Tag: "6.1.4"}, reference: &ociv1.OCIRepositoryRef{Tag: "6.1.4"},
digest: img4.digest.String(), beforeFunc: func(obj *ociv1.OCIRepository, tag, revision string) {
beforeFunc: func(obj *ociv1.OCIRepository) {
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, "VerifyFailed", "fail msg") conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, "VerifyFailed", "fail msg")
obj.Spec.Verify = nil obj.Spec.Verify = nil
obj.Status.Artifact = &sourcev1.Artifact{Revision: fmt.Sprintf("%s@%s", img4.tag, img4.digest.String())} obj.Status.Artifact = &sourcev1.Artifact{Revision: fmt.Sprintf("%s@%s", tag, revision)}
}, },
want: sreconcile.ResultSuccess, want: sreconcile.ResultSuccess,
}, },
{ {
name: "same artifact, verified before, change in obj gen verify again", name: "same artifact, verified before, change in obj gen verify again",
reference: &ociv1.OCIRepositoryRef{Tag: "6.1.4"}, reference: &ociv1.OCIRepositoryRef{Tag: "6.1.4"},
digest: img4.digest.String(),
shouldSign: true, shouldSign: true,
beforeFunc: func(obj *ociv1.OCIRepository) { beforeFunc: func(obj *ociv1.OCIRepository, tag, revision string) {
obj.Status.Artifact = &sourcev1.Artifact{Revision: fmt.Sprintf("%s@%s", img4.tag, img4.digest.String())} obj.Status.Artifact = &sourcev1.Artifact{Revision: fmt.Sprintf("%s@%s", tag, revision)}
// Set Verified with old observed generation and different reason/message. // Set Verified with old observed generation and different reason/message.
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, "Verified", "verified") conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, "Verified", "verified")
// Set new object generation. // Set new object generation.
@ -1131,11 +1156,10 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
{ {
name: "no verify for already verified, verified condition remains the same", name: "no verify for already verified, verified condition remains the same",
reference: &ociv1.OCIRepositoryRef{Tag: "6.1.4"}, reference: &ociv1.OCIRepositoryRef{Tag: "6.1.4"},
digest: img4.digest.String(),
shouldSign: true, shouldSign: true,
beforeFunc: func(obj *ociv1.OCIRepository) { beforeFunc: func(obj *ociv1.OCIRepository, tag, revision string) {
// Artifact present and custom verified condition reason/message. // Artifact present and custom verified condition reason/message.
obj.Status.Artifact = &sourcev1.Artifact{Revision: fmt.Sprintf("%s@%s", img4.tag, img4.digest.String())} obj.Status.Artifact = &sourcev1.Artifact{Revision: fmt.Sprintf("%s@%s", tag, revision)}
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, "Verified", "verified") conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, "Verified", "verified")
}, },
want: sreconcile.ResultSuccess, want: sreconcile.ResultSuccess,
@ -1144,19 +1168,17 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
}, },
}, },
{ {
name: "insecure registries are not supported", name: "signed image on an insecure registry passes verification",
reference: &ociv1.OCIRepositoryRef{ reference: &ociv1.OCIRepositoryRef{
Tag: "6.1.4", Tag: "6.1.6",
}, },
digest: img4.digest.String(),
shouldSign: true, shouldSign: true,
insecure: true, insecure: true,
wantErr: true, want: sreconcile.ResultSuccess,
want: sreconcile.ResultEmpty,
assertConditions: []metav1.Condition{ assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"), *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"), *conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, sourcev1.VerificationError, "cosign does not support insecure registries"), *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of revision <revision>"),
}, },
}, },
} }
@ -1179,6 +1201,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
keys, err := cosign.GenerateKeyPair(pf) keys, err := cosign.GenerateKeyPair(pf)
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
tmpDir := t.TempDir()
err = os.WriteFile(path.Join(tmpDir, "cosign.key"), keys.PrivateBytes, 0600) err = os.WriteFile(path.Join(tmpDir, "cosign.key"), keys.PrivateBytes, 0600)
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
@ -1190,15 +1213,34 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
"cosign.pub": keys.PublicBytes, "cosign.pub": keys.PublicBytes,
}} }}
err = r.Create(ctx, secret) g.Expect(r.Create(ctx, secret)).NotTo(HaveOccurred())
if err != nil {
g.Expect(err).NotTo(HaveOccurred()) caSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "ca-cert-cosign",
Generation: 1,
},
Data: map[string][]byte{
"caFile": tlsCA,
},
} }
g.Expect(r.Create(ctx, caSecret)).ToNot(HaveOccurred())
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t) g := NewWithT(t)
workspaceDir := t.TempDir()
regOpts := registryOptions{
withTLS: !tt.insecure,
}
server, err := setupRegistryServer(ctx, workspaceDir, regOpts)
g.Expect(err).NotTo(HaveOccurred())
t.Cleanup(func() {
server.Close()
})
obj := &ociv1.OCIRepository{ obj := &ociv1.OCIRepository{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
GenerateName: "verify-oci-source-signature-", GenerateName: "verify-oci-source-signature-",
@ -1216,6 +1258,10 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
if tt.insecure { if tt.insecure {
obj.Spec.Insecure = true obj.Spec.Insecure = true
} else {
obj.Spec.CertSecretRef = &meta.LocalObjectReference{
Name: "ca-cert-cosign",
}
} }
if !tt.keyless { if !tt.keyless {
@ -1226,12 +1272,15 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
obj.Spec.Reference = tt.reference obj.Spec.Reference = tt.reference
} }
podinfoVersions, err := pushMultiplePodinfoImages(server.registryHost, tt.insecure, tt.reference.Tag)
g.Expect(err).ToNot(HaveOccurred())
keychain, err := r.keychain(ctx, obj) keychain, err := r.keychain(ctx, obj)
if err != nil { if err != nil {
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
} }
opts := craneOptions(ctx, true) opts := craneOptions(ctx, false)
opts = append(opts, crane.WithAuthFromKeychain(keychain)) opts = append(opts, crane.WithAuthFromKeychain(keychain))
artifactURL, err := r.getArtifactURL(obj, opts) artifactURL, err := r.getArtifactURL(obj, opts)
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
@ -1250,21 +1299,22 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
SkipConfirmation: true, SkipConfirmation: true,
TlogUpload: false, TlogUpload: false,
Registry: coptions.RegistryOptions{Keychain: keychain, AllowInsecure: true}, Registry: coptions.RegistryOptions{Keychain: keychain, AllowInsecure: true, AllowHTTPRegistry: tt.insecure},
}, []string{artifactURL}) }, []string{artifactURL})
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
} }
image := podinfoVersions[tt.reference.Tag]
assertConditions := tt.assertConditions assertConditions := tt.assertConditions
for k := range assertConditions { for k := range assertConditions {
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<revision>", fmt.Sprintf("%s@%s", tt.reference.Tag, tt.digest)) assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<revision>", fmt.Sprintf("%s@%s", tt.reference.Tag, image.digest.String()))
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<url>", artifactURL) assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<url>", artifactURL)
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<provider>", "cosign") assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<provider>", "cosign")
} }
if tt.beforeFunc != nil { if tt.beforeFunc != nil {
tt.beforeFunc(obj) tt.beforeFunc(obj, image.tag, image.digest.String())
} }
g.Expect(r.Client.Create(ctx, obj)).ToNot(HaveOccurred()) g.Expect(r.Client.Create(ctx, obj)).ToNot(HaveOccurred())
@ -1297,8 +1347,11 @@ func TestOCIRepository_reconcileSource_noop(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
server, err := setupRegistryServer(ctx, tmpDir, registryOptions{}) server, err := setupRegistryServer(ctx, tmpDir, registryOptions{})
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(func() {
server.Close()
})
_, err = pushMultiplePodinfoImages(server.registryHost, "6.1.5") _, err = pushMultiplePodinfoImages(server.registryHost, true, "6.1.5")
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
// NOTE: The following verifies if it was a noop run by checking the // NOTE: The following verifies if it was a noop run by checking the
@ -1431,6 +1484,7 @@ func TestOCIRepository_reconcileSource_noop(t *testing.T) {
Reference: &ociv1.OCIRepositoryRef{Tag: "6.1.5"}, Reference: &ociv1.OCIRepositoryRef{Tag: "6.1.5"},
Interval: metav1.Duration{Duration: interval}, Interval: metav1.Duration{Duration: interval},
Timeout: &metav1.Duration{Duration: timeout}, Timeout: &metav1.Duration{Duration: timeout},
Insecure: true,
}, },
} }
@ -1709,9 +1763,11 @@ func TestOCIRepository_getArtifactURL(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
server, err := setupRegistryServer(ctx, tmpDir, registryOptions{}) server, err := setupRegistryServer(ctx, tmpDir, registryOptions{})
g.Expect(err).ToNot(HaveOccurred()) t.Cleanup(func() {
server.Close()
})
imgs, err := pushMultiplePodinfoImages(server.registryHost, "6.1.4", "6.1.5", "6.1.6") imgs, err := pushMultiplePodinfoImages(server.registryHost, true, "6.1.4", "6.1.5", "6.1.6")
g.Expect(err).ToNot(HaveOccurred()) g.Expect(err).ToNot(HaveOccurred())
tests := []struct { tests := []struct {
@ -1778,6 +1834,7 @@ func TestOCIRepository_getArtifactURL(t *testing.T) {
URL: tt.url, URL: tt.url,
Interval: metav1.Duration{Duration: interval}, Interval: metav1.Duration{Duration: interval},
Timeout: &metav1.Duration{Duration: timeout}, Timeout: &metav1.Duration{Duration: timeout},
Insecure: true,
}, },
} }
@ -2299,11 +2356,25 @@ func createPodinfoImageFromTar(tarFileName, tag, registryURL string, opts ...cra
}, nil }, nil
} }
func pushMultiplePodinfoImages(serverURL string, versions ...string) (map[string]podinfoImage, error) { func pushMultiplePodinfoImages(serverURL string, insecure bool, versions ...string) (map[string]podinfoImage, error) {
podinfoVersions := make(map[string]podinfoImage) podinfoVersions := make(map[string]podinfoImage)
var opts []crane.Option
// If the registry is insecure then instruct configure an insecure HTTP client,
// otherwise add the root CA certificate since the HTTPS server is self signed.
if insecure {
opts = append(opts, crane.Insecure)
} else {
transport := http.DefaultTransport.(*http.Transport)
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(tlsCA)
transport.TLSClientConfig = &tls.Config{
RootCAs: pool,
}
opts = append(opts, crane.WithTransport(transport))
}
for i := 0; i < len(versions); i++ { for i := 0; i < len(versions); i++ {
pi, err := createPodinfoImageFromTar(fmt.Sprintf("podinfo-%s.tar", versions[i]), versions[i], serverURL) pi, err := createPodinfoImageFromTar(fmt.Sprintf("podinfo-%s.tar", versions[i]), versions[i], serverURL, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2362,75 +2433,6 @@ func createCert(template, parent *x509.Certificate, pub interface{}, parentPriv
return return
} }
func createTLSServer() (*httptest.Server, []byte, []byte, []byte, tls.Certificate, error) {
var clientTLSCert tls.Certificate
var rootCertPEM, clientCertPEM, clientKeyPEM []byte
srv := httptest.NewUnstartedServer(registry.New())
// Create a self-signed cert to use as the CA and server cert.
rootKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
}
rootCertTmpl, err := certTemplate()
if err != nil {
return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
}
rootCertTmpl.IsCA = true
rootCertTmpl.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature
rootCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
rootCertTmpl.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
var rootCert *x509.Certificate
rootCert, rootCertPEM, err = createCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey)
if err != nil {
return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
}
rootKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rootKey),
})
// Create a TLS cert using the private key and certificate.
rootTLSCert, err := tls.X509KeyPair(rootCertPEM, rootKeyPEM)
if err != nil {
return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
}
// To trust a client certificate, the server must be given a
// CA cert pool.
pool := x509.NewCertPool()
pool.AddCert(rootCert)
srv.TLS = &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{rootTLSCert},
ClientCAs: pool,
}
// Create a client cert, signed by the "CA".
clientKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
}
clientCertTmpl, err := certTemplate()
if err != nil {
return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
}
clientCertTmpl.KeyUsage = x509.KeyUsageDigitalSignature
clientCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
_, clientCertPEM, err = createCert(clientCertTmpl, rootCert, &clientKey.PublicKey, rootKey)
if err != nil {
return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
}
// Encode and load the cert and private key for the client.
clientKeyPEM = pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey),
})
clientTLSCert, err = tls.X509KeyPair(clientCertPEM, clientKeyPEM)
return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
}
func TestOCIContentConfigChanged(t *testing.T) { func TestOCIContentConfigChanged(t *testing.T) {
tests := []struct { tests := []struct {
name string name string

View File

@ -21,12 +21,16 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log"
"math/rand" "math/rand"
"net"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
"github.com/foxcpp/go-mockdns"
"github.com/phayes/freeport" "github.com/phayes/freeport"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -95,9 +99,11 @@ var (
) )
var ( var (
tlsPublicKey []byte tlsPublicKey []byte
tlsPrivateKey []byte tlsPrivateKey []byte
tlsCA []byte tlsCA []byte
clientPublicKey []byte
clientPrivateKey []byte
) )
var ( var (
@ -114,11 +120,18 @@ type registryClientTestServer struct {
registryHost string registryHost string
workspaceDir string workspaceDir string
registryClient *helmreg.Client registryClient *helmreg.Client
dnsServer *mockdns.Server
} }
type registryOptions struct { type registryOptions struct {
withBasicAuth bool withBasicAuth bool
withTLS bool withTLS bool
withClientCertAuth bool
// Allow disbaling DNS mocking since Helm OCI doesn't yet suppot
// insecure OCI registries, which means we need Docker's automatic
// connection downgrading if the registry is hosted on localhost.
// Once Helm OCI supports insecure registries, we can get rid of this.
disableDNSMocking bool
} }
func setupRegistryServer(ctx context.Context, workspaceDir string, opts registryOptions) (*registryClientTestServer, error) { func setupRegistryServer(ctx context.Context, workspaceDir string, opts registryOptions) (*registryClientTestServer, error) {
@ -150,7 +163,28 @@ func setupRegistryServer(ctx context.Context, workspaceDir string, opts registry
} }
server.registryHost = fmt.Sprintf("localhost:%d", port) server.registryHost = fmt.Sprintf("localhost:%d", port)
config.HTTP.Addr = fmt.Sprintf("127.0.0.1:%d", port)
// Change the registry host to a host which is not localhost and
// mock DNS to map example.com to 127.0.0.1.
// This is required because Docker enforces HTTP if the registry
// is hosted on localhost/127.0.0.1.
if !opts.disableDNSMocking {
server.registryHost = fmt.Sprintf("example.com:%d", port)
// Disable DNS server logging as it is extremely chatty.
dnsLog := log.Default()
dnsLog.SetOutput(ioutil.Discard)
server.dnsServer, err = mockdns.NewServerWithLogger(map[string]mockdns.Zone{
"example.com.": {
A: []string{"127.0.0.1"},
},
}, dnsLog, false)
if err != nil {
return nil, err
}
server.dnsServer.PatchNet(net.DefaultResolver)
}
config.HTTP.Addr = fmt.Sprintf(":%d", port)
config.HTTP.DrainTimeout = time.Duration(10) * time.Second config.HTTP.DrainTimeout = time.Duration(10) * time.Second
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
@ -178,6 +212,10 @@ func setupRegistryServer(ctx context.Context, workspaceDir string, opts registry
if opts.withTLS { if opts.withTLS {
config.HTTP.TLS.Certificate = "testdata/certs/server.pem" config.HTTP.TLS.Certificate = "testdata/certs/server.pem"
config.HTTP.TLS.Key = "testdata/certs/server-key.pem" config.HTTP.TLS.Key = "testdata/certs/server-key.pem"
// Configure CA certificates only if client cert authentication is enabled.
if opts.withClientCertAuth {
config.HTTP.TLS.ClientCAs = []string{"testdata/certs/ca.pem"}
}
} }
// setup logger options // setup logger options
@ -198,6 +236,13 @@ func setupRegistryServer(ctx context.Context, workspaceDir string, opts registry
return server, nil return server, nil
} }
func (r *registryClientTestServer) Close() {
if r.dnsServer != nil {
mockdns.UnpatchNet(net.DefaultResolver)
r.dnsServer.Close()
}
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
initTestTLS() initTestTLS()
@ -229,11 +274,13 @@ func TestMain(m *testing.M) {
panic(fmt.Sprintf("failed to create workspace directory: %v", err)) panic(fmt.Sprintf("failed to create workspace directory: %v", err))
} }
testRegistryServer, err = setupRegistryServer(ctx, testWorkspaceDir, registryOptions{ testRegistryServer, err = setupRegistryServer(ctx, testWorkspaceDir, registryOptions{
withBasicAuth: true, withBasicAuth: true,
disableDNSMocking: true,
}) })
if err != nil { if err != nil {
panic(fmt.Sprintf("Failed to create a test registry server: %v", err)) panic(fmt.Sprintf("Failed to create a test registry server: %v", err))
} }
defer testRegistryServer.Close()
if err := (&GitRepositoryReconciler{ if err := (&GitRepositoryReconciler{
Client: testEnv, Client: testEnv,
@ -355,6 +402,14 @@ func initTestTLS() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
clientPrivateKey, err = os.ReadFile("testdata/certs/client-key.pem")
if err != nil {
panic(err)
}
clientPublicKey, err = os.ReadFile("testdata/certs/client.pem")
if err != nil {
panic(err)
}
} }
func newTestStorage(s *testserver.HTTPServer) (*Storage, error) { func newTestStorage(s *testserver.HTTPServer) (*Storage, error) {

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
all: server-key.pem all: server-key.pem client-key.pem
ca-key.pem: ca-csr.json ca-key.pem: ca-csr.json
cfssl gencert -initca ca-csr.json | cfssljson -bare ca cfssl gencert -initca ca-csr.json | cfssljson -bare ca
@ -28,3 +28,13 @@ server-key.pem: server-csr.json ca-config.json ca-key.pem
server-csr.json | cfssljson -bare server server-csr.json | cfssljson -bare server
sever.pem: server-key.pem sever.pem: server-key.pem
server.csr: server-key.pem server.csr: server-key.pem
client-key.pem: client-csr.json ca-config.json ca-key.pem
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-profile=web-servers \
client-csr.json | cfssljson -bare client
client.pem: client-key.pem
client.csr: client-key.pem

View File

@ -0,0 +1,9 @@
{
"CN": "example.com",
"hosts": [
"127.0.0.1",
"localhost",
"example.com",
"www.example.com"
]
}

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICpqb1p1TH98yoFXEEt6JmWc/Snb8NaYyz8jfTOVDBLOoAoGCCqGSM49
AwEHoUQDQgAERjzob4CCuyv+cYPyTYCPHwGuqSNGNuX3UGWpxvzwEqjYEWiePlOz
eJLk4DWaVX8CmVakNLsK/EHnBv9ErG7QYQ==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,8 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBHDCBwwIBADAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTBZMBMGByqGSM49AgEG
CCqGSM49AwEHA0IABEY86G+Agrsr/nGD8k2Ajx8BrqkjRjbl91Blqcb88BKo2BFo
nj5Ts3iS5OA1mlV/AplWpDS7CvxB5wb/RKxu0GGgSzBJBgkqhkiG9w0BCQ4xPDA6
MDgGA1UdEQQxMC+CCWxvY2FsaG9zdIILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxl
LmNvbYcEfwAAATAKBggqhkjOPQQDAgNIADBFAiAHmtr9fDDx5eyFfY7r5m8xA4Wh
Jm+TB6/czvXRNNOKzAIhAN7ln6BpneEm2oqIBGqvfc3pETC6jdGJxCfYw+X+7von
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB7DCCAZKgAwIBAgIUPJmKtZ6CfSxybx2BSsVS5EVun0swCgYIKoZIzj0EAwIw
GTEXMBUGA1UEAxMOZXhhbXBsZS5jb20gQ0EwHhcNMjMwNzE5MTExMzAwWhcNMzMw
NzE2MTExMzAwWjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTBZMBMGByqGSM49AgEG
CCqGSM49AwEHA0IABEY86G+Agrsr/nGD8k2Ajx8BrqkjRjbl91Blqcb88BKo2BFo
nj5Ts3iS5OA1mlV/AplWpDS7CvxB5wb/RKxu0GGjgbowgbcwDgYDVR0PAQH/BAQD
AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAA
MB0GA1UdDgQWBBTgAyCQoH/EJqz/nY5DJa/uvWWshzAfBgNVHSMEGDAWgBQGyUiU
1QEZiMAqjsnIYTwZ4yp5wzA4BgNVHREEMTAvgglsb2NhbGhvc3SCC2V4YW1wbGUu
Y29tgg93d3cuZXhhbXBsZS5jb22HBH8AAAEwCgYIKoZIzj0EAwIDSAAwRQIgKSJH
YvhKiXcUUzRoL6FsXQeAlhemSg3lD9se+BhRF8ECIQDx2UpWFLDe5NOPqhrcR1Sd
haFriAG8eR1yD3u3nJvY6g==
-----END CERTIFICATE-----