diff --git a/adapter/pkg/logging/logging_constant.go b/adapter/pkg/logging/logging_constant.go index a10250385..9e48604ab 100644 --- a/adapter/pkg/logging/logging_constant.go +++ b/adapter/pkg/logging/logging_constant.go @@ -186,3 +186,14 @@ const ( Error3125 = 3125 Error3126 = 3126 ) + +// Error codes api (3200-3299) +const ( + Error3200 = 3200 + Error3201 = 3201 + Error3202 = 3202 + Error3203 = 3203 + Error3204 = 3204 + Error3205 = 3205 + Error3206 = 3206 +) diff --git a/common-controller/Dockerfile b/common-controller/Dockerfile index c0808d211..b1ca78a2b 100644 --- a/common-controller/Dockerfile +++ b/common-controller/Dockerfile @@ -68,4 +68,6 @@ COPY resources/check_health.sh . COPY resources/conf/log_config.toml conf/ COPY ./${TARGETARCH}/main common-controller +EXPOSE 9543 + CMD ./common-controller diff --git a/common-controller/cmd/main.go b/common-controller/cmd/main.go index fc8c440ba..c116218f4 100644 --- a/common-controller/cmd/main.go +++ b/common-controller/cmd/main.go @@ -20,11 +20,14 @@ package main import ( logger "github.com/sirupsen/logrus" commoncontroller "github.com/wso2/apk/common-controller/commoncontroller" + web "github.com/wso2/apk/common-controller/internal/web" config "github.com/wso2/apk/common-controller/internal/config" ) func main() { - logger.Info("Starting the Common Controller") conf := config.ReadConfigs() + logger.Info("Starting the Web server") + go web.StartWebServer(); + logger.Info("Starting the Common Controller") commoncontroller.InitCommonControllerServer(conf) } diff --git a/common-controller/go.mod b/common-controller/go.mod index 082db94b0..41f019e42 100644 --- a/common-controller/go.mod +++ b/common-controller/go.mod @@ -14,7 +14,9 @@ require ( require ( github.com/envoyproxy/go-control-plane v0.11.2-0.20230802074621-eea0b3bd0f81 + github.com/gin-gonic/gin v1.9.1 github.com/pelletier/go-toml v1.8.1 + github.com/redis/go-redis/v9 v9.2.1 github.com/wso2/apk/adapter v0.0.0-20230811031118-fa0d1ec8848c golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b google.golang.org/grpc v1.57.0 @@ -22,7 +24,26 @@ require ( replace github.com/wso2/apk/adapter => ../adapter -require github.com/pmezard/go-difflib v1.0.0 // indirect +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect +) require ( github.com/beorn7/perks v1.0.1 // indirect diff --git a/common-controller/go.sum b/common-controller/go.sum index 2c6cac285..4080541a0 100644 --- a/common-controller/go.sum +++ b/common-controller/go.sum @@ -5,11 +5,19 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -20,6 +28,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= @@ -35,6 +45,12 @@ github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -46,8 +62,17 @@ github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTr github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -93,6 +118,9 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -101,8 +129,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -118,6 +150,8 @@ github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -132,6 +166,8 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -148,8 +184,14 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -161,9 +203,14 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= @@ -205,8 +252,10 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -304,6 +353,7 @@ k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5F k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= sigs.k8s.io/gateway-api v0.7.1 h1:Tts2jeepVkPA5rVG/iO+S43s9n7Vp7jCDhZDQYtPigQ= diff --git a/common-controller/internal/config/types.go b/common-controller/internal/config/types.go index cffb79abf..acf9f2ee9 100644 --- a/common-controller/internal/config/types.go +++ b/common-controller/internal/config/types.go @@ -41,6 +41,9 @@ type commoncontroller struct { // Trusted Certificates Truststore truststore Environment string + Redis redis + Sts sts + WebServer webServer } type keystore struct { @@ -59,3 +62,24 @@ type server struct { type operator struct { Namespaces []string } + +type redis struct { + Host string + Port string + Username string + Password string + UserCertPath string + UserKeyPath string + CACertPath string + TLSEnabled bool + RevokedTokenChannel string +} + +type sts struct { + AuthKeyPath string + AuthKeyHeader string +} + +type webServer struct { + Port int64 +} diff --git a/common-controller/internal/utils/tls_utils.go b/common-controller/internal/utils/tls_utils.go index e3c56e8d0..ae019edf3 100644 --- a/common-controller/internal/utils/tls_utils.go +++ b/common-controller/internal/utils/tls_utils.go @@ -86,7 +86,7 @@ func GetTrustedCertPool(truststoreLocation string) *x509.CertPool { return nil }) if err != nil { - logger.LoggerTLSUtils.Warn("Error walking the path %s", truststoreLocation, err) + logger.LoggerTLSUtils.Warnf("Error walking the path %s. Error: %s", truststoreLocation, err) } }) return caCertPool diff --git a/common-controller/internal/web/notify_handler.go b/common-controller/internal/web/notify_handler.go new file mode 100644 index 000000000..8e222eda5 --- /dev/null +++ b/common-controller/internal/web/notify_handler.go @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * 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 web + +import ( + "fmt" + "context" + "github.com/gin-gonic/gin" + "github.com/redis/go-redis/v9" + "net/http" + "time" + "crypto/tls" + "os" + "crypto/x509" + loggers "github.com/wso2/apk/common-controller/internal/loggers" + "github.com/wso2/apk/adapter/pkg/logging" + config "github.com/wso2/apk/common-controller/internal/config" + "io/ioutil" + "strings" + "encoding/base64" + "encoding/json" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +const tokenRevocationType = "TOKEN_REVOCATION" + +type revokeRequest struct { + Token string `json:"token"` + Jti string `json:"jti"` + Expiry int64 `json:"expiry"` +} + +type jWTClaims struct { + Jti string `json:"jti"` + Exp int64 `json:"exp"` +} + +var ( + redisAddr string + redisUsername string + redisPassword string + redisUserCertPath string + redisUserKeyPath string + redisCACertPath string + isTLSEnabled bool + redisRevokedTokenChannel string + tokenExpiryDivider = "_##_" + authKeyPath string + authKeyHeader string + rdb *redis.Client +) + +func init() { + conf := config.ReadConfigs() + redisHost := conf.CommonController.Redis.Host + redisPort := conf.CommonController.Redis.Port + redisAddr = redisHost + ":" + redisPort + redisUsername = conf.CommonController.Redis.Username + redisPassword = conf.CommonController.Redis.Password + redisUserCertPath = conf.CommonController.Redis.UserCertPath + redisUserKeyPath = conf.CommonController.Redis.UserKeyPath + redisCACertPath = conf.CommonController.Redis.CACertPath + isTLSEnabled = conf.CommonController.Redis.TLSEnabled + redisRevokedTokenChannel = conf.CommonController.Redis.RevokedTokenChannel + authKeyPath = conf.CommonController.Sts.AuthKeyPath + authKeyHeader = conf.CommonController.Sts.AuthKeyHeader + utilruntime.Must(initRedisClient()) +} + +// initRedisClient initializes the redis connection +func initRedisClient() error { + if isTLSEnabled { + cert, err := tls.LoadX509KeyPair(redisUserCertPath, redisUserKeyPath) + if err != nil { + return err; + } + caCert, err := os.ReadFile(redisCACertPath) + if err != nil { + return err; + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + rdb = redis.NewClient(&redis.Options{ + Addr: redisAddr, + Username: redisUsername, + Password: redisPassword, + TLSConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + }, + }) + } else { + rdb = redis.NewClient(&redis.Options{ + Addr: redisAddr, + Username: redisUsername, + Password: redisPassword, + }) + } + return nil; +} + +// NotifyHandler handles notify requests +func NotifyHandler(c *gin.Context) { + _type := c.Query("type") + if _type == tokenRevocationType { + revokeToken(c) + return + } + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid type"}) + return +} + +func revokeToken(c *gin.Context) { + if !authenticateTokenRevocationRequest(c) { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized request"}) + return + } + + var request revokeRequest + if err := c.ShouldBindJSON(&request); err != nil { + loggers.LoggerAPI.ErrorC(logging.PrintError(logging.Error3200, logging.MAJOR, "Error while parsing body: %v", err)) + c.JSON(http.StatusBadRequest, gin.H{"error": "Error while parsing json payload"}) + return + } + var jti string; + var expiry int64; + if request.Token != "" { + claims, err := extractClaimsFromJWT(request.Token) + if err != nil { + loggers.LoggerAPI.ErrorC(logging.PrintError(logging.Error3203, logging.MAJOR, "Error decoding token: %v", err)) + c.JSON(http.StatusBadRequest, gin.H{"error": "Error decoding token"}) + return + } + jti = claims.Jti + expiry = claims.Exp + } else { + jti = request.Jti + expiry = request.Expiry + } + if expiry <= time.Now().Unix() { + c.JSON(http.StatusBadRequest, gin.H{"error": "Token is already expired"}) + return + } + err := storeTokenInRedis(jti, expiry) + if (err != nil) { + loggers.LoggerAPI.ErrorC(logging.PrintError(logging.Error3202, logging.MAJOR, "Error adding revoked tokens to redis: %v", err)) + c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to store the token in Redis cache"}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Token revoked successfully"}) +} + +func generateKey(jti string) string { + return fmt.Sprintf("%s:%s", "wso2:apk:revoked_token", jti) +} + +func storeTokenInRedis(token string, expiry int64) error { + key := generateKey(token) + err := rdb.Do(context.Background(), "set", key, expiry, "EXAT", expiry).Err() + if err != nil { + return err + } + publishValue := fmt.Sprintf("%s%s%d", token, tokenExpiryDivider, expiry) + err = rdb.Do(context.Background(), "publish", redisRevokedTokenChannel, publishValue).Err() + if err != nil { + return err + } + return nil +} + +func authenticateTokenRevocationRequest(c *gin.Context) bool { + fileContent, err := ioutil.ReadFile(authKeyPath) + if err != nil { + loggers.LoggerAPI.ErrorC(logging.PrintError(logging.Error3204, logging.MAJOR, "Error reading shared key file: %v", err)) + return false + } + headerValue := c.GetHeader(authKeyHeader) + if headerValue == "" { + loggers.LoggerAPI.ErrorC(logging.PrintError(logging.Error3205, logging.MAJOR, "Unauthorized: Missing header")) + return false + } + if !strings.EqualFold(string(fileContent), headerValue) { + loggers.LoggerAPI.ErrorC(logging.PrintError(logging.Error3206, logging.MAJOR, "Unauthorized: Invalid header value")) + return false + } + return true +} + +func extractClaimsFromJWT(jwtToken string) (jWTClaims, error) { + var claims jWTClaims + parts := strings.Split(jwtToken, ".") + if len(parts) != 3 { + loggers.LoggerAPI.ErrorC(logging.PrintError(logging.Error3205, logging.MAJOR, "Invalid JWT")) + return claims, fmt.Errorf("Invalid JWT token") + } + payload, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + loggers.LoggerAPI.ErrorC(logging.PrintError(logging.Error3205, logging.MAJOR, "Invalid JWT")) + return claims, fmt.Errorf("Error decoding payload: %v", err) + } + err = json.Unmarshal(payload, &claims) + if err != nil { + return claims, fmt.Errorf("Error parsing JSON: %v", err) + } + return claims, nil +} diff --git a/common-controller/internal/web/web_server.go b/common-controller/internal/web/web_server.go new file mode 100644 index 000000000..a669e262b --- /dev/null +++ b/common-controller/internal/web/web_server.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * 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 web + +import ( + "github.com/gin-gonic/gin" + loggers "github.com/wso2/apk/common-controller/internal/loggers" + config "github.com/wso2/apk/common-controller/internal/config" + "fmt" +) + +// StartWebServer starts the web server +func StartWebServer() { + loggers.LoggerAPI.Info("Starting web server") + gin.SetMode(gin.ReleaseMode) + router := gin.Default() + router.POST("/notify", NotifyHandler) + conf := config.ReadConfigs() + certPath := conf.CommonController.Keystore.CertPath + keyPath := conf.CommonController.Keystore.KeyPath + port := conf.CommonController.WebServer.Port + router.RunTLS(fmt.Sprintf(":%d", port), certPath, keyPath) +} diff --git a/gateway/enforcer/org.wso2.apk.enforcer/build.gradle b/gateway/enforcer/org.wso2.apk.enforcer/build.gradle index cc67d72b8..36e56a3e3 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/build.gradle +++ b/gateway/enforcer/org.wso2.apk.enforcer/build.gradle @@ -80,7 +80,7 @@ dependencies { // Added as direct dependency for transitive dependency version upgrades implementation libs.reactor.netty.http implementation libs.protobuf.java - + implementation libs.jedis test { implementation libs.junit implementation libs.mockito.inline diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/config/EnvVarConfig.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/config/EnvVarConfig.java index c1e50eeca..85b4c2ead 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/config/EnvVarConfig.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/config/EnvVarConfig.java @@ -40,6 +40,17 @@ public class EnvVarConfig { public static final String XDS_MAX_RETRIES = "XDS_MAX_RETRIES"; public static final String XDS_RETRY_PERIOD = "XDS_RETRY_PERIOD"; public static final String HOSTNAME = "HOSTNAME"; + public static final String REDIS_USERNAME = "REDIS_USERNAME"; + public static final String REDIS_PASSWORD = "REDIS_PASSWORD"; + public static final String REDIS_HOST = "REDIS_HOST"; + public static final String REDIS_PORT = "REDIS_PORT"; + public static final String IS_REDIS_TLS_ENABLED = "IS_REDIS_TLS_ENABLED"; + public static final String REDIS_REVOKED_TOKENS_CHANNEL = "REDIS_REVOKED_TOKENS_CHANNEL"; + public static final String REDIS_KEY_FILE = "REDIS_KEY_FILE"; + public static final String REDIS_CERT_FILE = "REDIS_CERT_FILE"; + public static final String REDIS_CA_CERT_FILE = "REDIS_CA_CERT_FILE"; + public static final String REVOKED_TOKEN_CLEANUP_INTERVAL = "REVOKED_TOKEN_CLEANUP_INTERVAL"; + // Since the container is running in linux container, path separator is not needed. private static final String DEFAULT_TRUSTED_CA_CERTS_PATH = "/home/wso2/security/truststore"; @@ -55,7 +66,16 @@ public class EnvVarConfig { public static final String DEFAULT_XDS_MAX_RETRIES = Integer.toString(Constants.MAX_XDS_RETRIES); public static final String DEFAULT_XDS_RETRY_PERIOD = Integer.toString(Constants.XDS_DEFAULT_RETRY); public static final String DEFAULT_HOSTNAME = "Unassigned"; - + public static final String DEFAULT_REDIS_USERNAME = "default"; + public static final String DEFAULT_REDIS_PASSWORD = ""; + public static final String DEFAULT_REDIS_HOST = "redis-master"; + public static final int DEFAULT_REDIS_PORT = 6379; + public static final String DEFAULT_IS_REDIS_TLS_ENABLED = "false"; + public static final String DEFAULT_REDIS_REVOKED_TOKENS_CHANNEL = "wso2-apk-revoked-tokens-channel"; + public static final String DEFAULT_REDIS_KEY_FILE = "/home/wso2/security/redis/redis.key"; + public static final String DEFAULT_REDIS_CERT_FILE = "/home/wso2/security/redis/redis.crt"; + public static final String DEFAULT_REDIS_CA_CERT_FILE = "/home/wso2/security/redis/ca.crt"; + public static final int DEFAULT_REVOKED_TOKEN_CLEANUP_INTERVAL = 60*60; // In seconds private static EnvVarConfig instance; private final String trustedAdapterCertsPath; private final String trustDefaultCerts; @@ -74,6 +94,16 @@ public class EnvVarConfig { private final String xdsMaxRetries; private final String xdsRetryPeriod; private final String instanceIdentifier; + private final String redisUsername; + private final String redisPassword; + private final String redisHost; + private final int redisPort; + private final boolean isRedisTlsEnabled; + private final String revokedTokensRedisChannel; + private final String redisKeyFile; + private final String redisCertFile; + private final String redisCaCertFile; + private final int revokedTokenCleanupInterval; private EnvVarConfig() { trustedAdapterCertsPath = retrieveEnvVarOrDefault(TRUSTED_CA_CERTS_PATH, @@ -99,6 +129,17 @@ private EnvVarConfig() { // HOSTNAME environment property is readily available in docker and kubernetes, and it represents the Pod // name in Kubernetes context, containerID in docker context. instanceIdentifier = retrieveEnvVarOrDefault(HOSTNAME, DEFAULT_HOSTNAME); + redisUsername = retrieveEnvVarOrDefault(REDIS_USERNAME, DEFAULT_REDIS_USERNAME); + redisPassword = retrieveEnvVarOrDefault(REDIS_PASSWORD, DEFAULT_REDIS_PASSWORD); + redisHost = retrieveEnvVarOrDefault(REDIS_HOST, DEFAULT_REDIS_HOST); + redisPort = getRedisPortFromEnv(); + isRedisTlsEnabled = retrieveEnvVarOrDefault(IS_REDIS_TLS_ENABLED, DEFAULT_IS_REDIS_TLS_ENABLED).toLowerCase() + .equals(DEFAULT_IS_REDIS_TLS_ENABLED)? false:true; + revokedTokensRedisChannel = retrieveEnvVarOrDefault(REDIS_REVOKED_TOKENS_CHANNEL, DEFAULT_REDIS_REVOKED_TOKENS_CHANNEL); + redisKeyFile = retrieveEnvVarOrDefault(REDIS_KEY_FILE, DEFAULT_REDIS_KEY_FILE); + redisCertFile = retrieveEnvVarOrDefault(REDIS_CERT_FILE, DEFAULT_REDIS_CERT_FILE); + redisCaCertFile = retrieveEnvVarOrDefault(REDIS_CA_CERT_FILE, DEFAULT_REDIS_CA_CERT_FILE); + revokedTokenCleanupInterval = getRevokedTokenCleanupIntervalFromEnv(); } public static EnvVarConfig getInstance() { @@ -112,6 +153,23 @@ public static EnvVarConfig getInstance() { return instance; } + private int getRedisPortFromEnv() { + String portStr = retrieveEnvVarOrDefault(REDIS_PORT, String.valueOf(DEFAULT_REDIS_PORT)); + try { + return Integer.parseInt(portStr); + } catch (Exception e) { + return DEFAULT_REDIS_PORT; + } + } + + private int getRevokedTokenCleanupIntervalFromEnv() { + String intervalStr = retrieveEnvVarOrDefault(REVOKED_TOKEN_CLEANUP_INTERVAL, String.valueOf(DEFAULT_REVOKED_TOKEN_CLEANUP_INTERVAL)); + try { + return Integer.parseInt(intervalStr); + } catch (Exception e) { + return DEFAULT_REVOKED_TOKEN_CLEANUP_INTERVAL; + } + } private String retrieveEnvVarOrDefault(String variable, String defaultValue) { if (StringUtils.isEmpty(System.getenv(variable))) { @@ -180,4 +238,48 @@ public String getXdsRetryPeriod() { public String getInstanceIdentifier() { return instanceIdentifier; } + + public String getRedisUsername() { + + return redisUsername; + } + + public String getRedisPassword() { + + return redisPassword; + } + + public String getRedisHost() { + + return redisHost; + } + + public boolean isRedisTlsEnabled() { + + return isRedisTlsEnabled; + } + + public int getRedisPort() { + return redisPort; + } + + public String getRedisKeyFile() { + return redisKeyFile; + } + + public String getRedisCertFile() { + return redisCertFile; + } + + public String getRedisCaCertFile() { + return redisCaCertFile; + } + + public String getRevokedTokensRedisChannel() { + return revokedTokensRedisChannel; + } + + public int getRevokedTokenCleanupInterval() { + return revokedTokenCleanupInterval; + } } diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/security/jwt/JWTAuthenticator.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/security/jwt/JWTAuthenticator.java index 8b2c836ef..aed6ab9f8 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/security/jwt/JWTAuthenticator.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/security/jwt/JWTAuthenticator.java @@ -51,6 +51,7 @@ import org.wso2.apk.enforcer.security.jwt.validator.JWTValidator; import org.wso2.apk.enforcer.security.jwt.validator.RevokedJWTDataHolder; import org.wso2.apk.enforcer.subscription.SubscriptionDataStoreImpl; +import org.wso2.apk.enforcer.server.RevokedTokenRedisClient; import org.wso2.apk.enforcer.tracing.TracingConstants; import org.wso2.apk.enforcer.tracing.TracingSpan; import org.wso2.apk.enforcer.tracing.TracingTracer; @@ -138,7 +139,13 @@ public AuthenticationContext authenticate(RequestContext requestContext) throws String environment = requestContext.getMatchedAPI().getEnvironment(); context = context + "/" + version; + JWTValidationInfo validationInfo = getJwtValidationInfo(jwtToken, organization, environment); + if (RevokedTokenRedisClient.getRevokedTokens().contains(validationInfo.getIdentifier())) { + log.info("Expired JWT token. ", validationInfo.getIdentifier()); + throw new APISecurityException(APIConstants.StatusCodes.UNAUTHENTICATED.getCode(), + APISecurityConstants.API_AUTH_INVALID_CREDENTIALS, "Invalid JWT token"); + } if (validationInfo != null) { if (validationInfo.isValid()) { Map claims = validationInfo.getClaims(); diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/AuthServer.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/AuthServer.java index aa8a40588..e380cf290 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/AuthServer.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/AuthServer.java @@ -49,7 +49,12 @@ import org.wso2.apk.enforcer.util.TLSUtils; import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLException; @@ -116,7 +121,8 @@ public static void main(String[] args) { } else { logger.debug("analytics filter is disabled."); } - + // Start receiving revoked tokens from redis cache + RevokedTokenRedisClient.retrieveAndSubscribe(); // Start the server server.start(); diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/RevokedTokenRedisClient.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/RevokedTokenRedisClient.java new file mode 100644 index 000000000..c418f9af2 --- /dev/null +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/RevokedTokenRedisClient.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you 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 org.wso2.apk.enforcer.server; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.wso2.apk.enforcer.commons.exception.EnforcerException; +import org.wso2.apk.enforcer.config.ConfigHolder; +import org.wso2.apk.enforcer.util.JWTUtils; +import org.wso2.apk.enforcer.util.TLSUtils; +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisClientConfig; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPubSub; +import redis.clients.jedis.params.ScanParams; +import redis.clients.jedis.resps.ScanResult; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; + +public class RevokedTokenRedisClient { + + private JedisPool jedisPool; + private Set revokedTokens; + private Queue> expiryQueue; + private static Set revokedTokensStatic; + private String redisRevokedTokensChannel; + private final ScheduledExecutorService revokedTokensCleanupScheduler = Executors.newScheduledThreadPool(1); + private int revokedTokenCleanupInterval; + private static volatile boolean isAlreadyStarted = false; + private static final Logger logger = LogManager.getLogger(RevokedTokenRedisClient.class); + private static final String TOKEN_EXPIRY_DIVIDER = "_##_"; + private static final String REVOKED_TOKEN_REDIS_KEY_PATTERN = "wso2:apk:revoked_token:*"; + private RevokedTokenRedisClient(Set revokedTokens, Queue> expiryQueue) throws EnforcerException { + this.revokedTokens = revokedTokens; + this.expiryQueue = expiryQueue; + + String userName = ConfigHolder.getInstance().getEnvVarConfig().getRedisUsername(); + String password = ConfigHolder.getInstance().getEnvVarConfig().getRedisPassword(); + String host = ConfigHolder.getInstance().getEnvVarConfig().getRedisHost(); + int port = ConfigHolder.getInstance().getEnvVarConfig().getRedisPort(); + boolean isSSLEnabled = ConfigHolder.getInstance().getEnvVarConfig().isRedisTlsEnabled(); + this.redisRevokedTokensChannel = ConfigHolder.getInstance().getEnvVarConfig().getRevokedTokensRedisChannel(); + this.revokedTokenCleanupInterval = ConfigHolder.getInstance().getEnvVarConfig().getRevokedTokenCleanupInterval(); + String caCert = ConfigHolder.getInstance().getEnvVarConfig().getRedisCaCertFile(); + DefaultJedisClientConfig.Builder builder = DefaultJedisClientConfig.builder() + .user(userName) + .password(password); + if (isSSLEnabled) { + SSLSocketFactory sslFactory = createSslSocketFactory(caCert); + builder = builder + .ssl(true) + .sslSocketFactory(sslFactory); + } + JedisClientConfig config = builder.build(); + + HostAndPort hostAndPort = new HostAndPort(host, port); + this.jedisPool = new JedisPool(hostAndPort, config); + + } + + public static void retrieveAndSubscribe() throws EnforcerException { + if (isAlreadyStarted) { + logger.debug("Already a token retreival task is running"); + return; + } + logger.debug("Starting redis revoked token client..."); + isAlreadyStarted = true; + + HashSet revokedTokens = new HashSet<>(); + Set synchronizedRevokedTokens = Collections.synchronizedSet(revokedTokens); + PriorityBlockingQueue> expiryQueue = + new PriorityBlockingQueue<>(10, Map.Entry.comparingByKey()); + RevokedTokenRedisClient revokedTokenRedisClient = + new RevokedTokenRedisClient(synchronizedRevokedTokens, expiryQueue); + + revokedTokensStatic = revokedTokens; + revokedTokenRedisClient.subscribe(); + revokedTokenRedisClient.retrieveAllRevokedTokens(); + revokedTokenRedisClient.scheduleCleanup(); + } + + private void scheduleCleanup() { + this.revokedTokensCleanupScheduler.scheduleAtFixedRate(this::startCleanupTask, this.revokedTokenCleanupInterval, + this.revokedTokenCleanupInterval, TimeUnit.SECONDS); + } + + private void subscribe() { + Thread jedisThread = new Thread(new RevokedTokenRedisSubscriber(this.jedisPool, + this.revokedTokens, this.expiryQueue, this.redisRevokedTokensChannel)); + jedisThread.start(); + } + + private void retrieveAllRevokedTokens() { + try (Jedis jedis = this.jedisPool.getResource()) { + String cursor = "0"; + Set keysAndValues = new HashSet<>(); + do { + ScanResult scanResult = jedis.scan(cursor, + new ScanParams().match(REVOKED_TOKEN_REDIS_KEY_PATTERN)); + keysAndValues.addAll(scanResult.getResult()); + cursor = scanResult.getCursor(); + } while (!cursor.equals("0")); + + for (String key : keysAndValues) { + try { + String value = jedis.get(key); + Long expiry = Long.valueOf(value); + String token = key.substring(REVOKED_TOKEN_REDIS_KEY_PATTERN.length()-1); + revokedTokens.add(token); + expiryQueue.offer(Map.entry(expiry, token)); + logger.debug("New token added. Token : " + token + " expiry: " + expiry); + } catch(Exception e) { + logger.warn("Error while processing key: " + key, e); + } + } + } + } + + private void startCleanupTask() { + long currentTime = System.currentTimeMillis() / 1000L; + while (!this.expiryQueue.isEmpty() && this.expiryQueue.peek().getKey() <= currentTime) { + Map.Entry entry = this.expiryQueue.poll(); + String token = entry.getValue(); + this.revokedTokens.remove(token); + logger.debug("Token removed: " + token + " expiry: " + entry.getKey()); + } + } + + static class RevokedTokenRedisSubscriber implements Runnable { + JedisPool jedisPool; + Set revokedTokens; + Queue> expiryQueue; + private String redisRevokedTokensChannel; + + public RevokedTokenRedisSubscriber(JedisPool pool, + Set revokedTokens, + Queue> expiryQueue, + String channel) { + this.jedisPool = pool; + this.revokedTokens = revokedTokens; + this.expiryQueue = expiryQueue; + this.redisRevokedTokensChannel = channel; + } + @Override + public void run() { + try(Jedis jedis = this.jedisPool.getResource()) { + jedis.connect(); + JedisPubSub jedisPubSub = new JedisPubSub() { + @Override + public void onMessage(String channel, String message) { + try { + logger.debug("Received message: " + message); + String[] tokenAndExpiry = message.split(TOKEN_EXPIRY_DIVIDER); + Long expiry = Long.valueOf(tokenAndExpiry[1]); + String token = tokenAndExpiry[0]; + revokedTokens.add(token); + expiryQueue.offer(Map.entry(expiry, token)); + } catch (Exception e) { + logger.error("Error while processing the token message in the redis " + + "subscriber. Exception: ", e); + } + } + + @Override + public void onUnsubscribe(String channel, int subscribedChannels) { + logger.debug("Channel: " + channel + " subscribed channels: " + subscribedChannels); + } + }; + jedis.subscribe(jedisPubSub, this.redisRevokedTokensChannel); + } catch (Exception e) { + logger.error("Error occured in the subscription connection trying to connect again."+ e); + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + logger.error("Exception while sleeping. Exception: " + ex); + } + run(); + } + + } + } + + public static Set getRevokedTokens() { + return revokedTokensStatic; + } + + public static void setRevokedTokens(SetrevokedTokensSet) { + revokedTokensStatic = revokedTokensSet; + } + + private static SSLSocketFactory createSslSocketFactory(String redisCaCertPath) throws EnforcerException { + try { + KeyStore trustStore = TLSUtils.getDefaultCertTrustStore(); + TLSUtils.addCertsToTruststore(trustStore, redisCaCertPath); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); + trustManagerFactory.init(trustStore); + Certificate cert = TLSUtils.getCertificateFromFile(ConfigHolder.getInstance().getEnvVarConfig().getEnforcerPublicKeyPath()); + Key key = JWTUtils.getPrivateKey(ConfigHolder.getInstance().getEnvVarConfig().getEnforcerPrivateKeyPath()); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setKeyEntry("client-keys", key, null, new Certificate[]{cert}); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, null); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + return sslContext.getSocketFactory(); + } catch (Exception e) { + throw new EnforcerException("Error while creating SSL socket factory.", e); + } + } + +} diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/test/java/org/wso2/apk/enforcer/jwt/JWTValidatorTest.java b/gateway/enforcer/org.wso2.apk.enforcer/src/test/java/org/wso2/apk/enforcer/jwt/JWTValidatorTest.java index 5997b312f..5e2466d9c 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/test/java/org/wso2/apk/enforcer/jwt/JWTValidatorTest.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/test/java/org/wso2/apk/enforcer/jwt/JWTValidatorTest.java @@ -19,6 +19,7 @@ import com.google.common.cache.LoadingCache; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.UUID; import org.apache.commons.logging.Log; @@ -26,6 +27,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -52,10 +54,16 @@ import org.wso2.apk.enforcer.security.KeyValidator; import org.wso2.apk.enforcer.security.jwt.JWTAuthenticator; import org.wso2.apk.enforcer.security.jwt.validator.JWTValidator; +import org.wso2.apk.enforcer.server.RevokedTokenRedisClient; import org.wso2.apk.enforcer.subscription.SubscriptionDataStoreImpl; public class JWTValidatorTest { + @Before + public void setup() { + RevokedTokenRedisClient.setRevokedTokens(new HashSet<>()); + } + @Test public void testJWTValidator() throws APISecurityException, EnforcerException { String organization = "org1"; diff --git a/helm-charts/templates/data-plane/gateway-components/common-controller/common-controller-deployment.yaml b/helm-charts/templates/data-plane/gateway-components/common-controller/common-controller-deployment.yaml index a1c80a0bc..5292da6c6 100644 --- a/helm-charts/templates/data-plane/gateway-components/common-controller/common-controller-deployment.yaml +++ b/helm-charts/templates/data-plane/gateway-components/common-controller/common-controller-deployment.yaml @@ -42,6 +42,8 @@ spec: ports: - containerPort: 18005 protocol: TCP + - containerPort: 9543 + protocol: TCP {{ include "apk-helm.deployment.resources" .Values.wso2.apk.dp.commonController.deployment.resources | indent 10 }} {{ include "apk-helm.deployment.env" .Values.wso2.apk.dp.commonController.deployment.env | indent 10 }} - name: OPERATOR_POD_NAMESPACE @@ -97,6 +99,9 @@ spec: - mountPath: /tmp/k8s-webhook-server/serving-certs name: adapter-webhook-server-cert readOnly: true + - mountPath: /home/wso2/security/sts/ + name: sts-shared-auth-key + readOnly: true readinessProbe: exec: command: [ "sh", "check_health.sh" ] @@ -138,6 +143,10 @@ spec: secret: secretName: {{ template "apk-helm.resource.prefix" . }}-webhook-server-cert defaultMode: 420 + - name: sts-shared-auth-key + secret: + secretName: {{ template "apk-helm.resource.prefix" . }}-sts-shared-auth-key + defaultMode: 420 {{ if and .Values.wso2.apk.dp.enabled .Values.wso2.apk.dp.ratelimiter.enabled }} - name: ratelimiter-truststore-secret-volume secret: diff --git a/helm-charts/templates/data-plane/gateway-components/common-controller/common-controller-sts-shared-auth-key.yaml b/helm-charts/templates/data-plane/gateway-components/common-controller/common-controller-sts-shared-auth-key.yaml new file mode 100644 index 000000000..f6dfecf99 --- /dev/null +++ b/helm-charts/templates/data-plane/gateway-components/common-controller/common-controller-sts-shared-auth-key.yaml @@ -0,0 +1,24 @@ +# Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +# +# WSO2 LLC. licenses this file to you 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. + +apiVersion: v1 +data: + auth_key.txt: MmpybXlwYWszOTF6c3F6OTc0dWdkZGRlYmY4MTJvZngxYjl0MW9xMjc1MzBpcjAydGM4MTVlZW1yeDQzNXF2Y3A0MXVjZ3k3djV1dWF3emk0cWNtanJ4MGsxemdveDJzMjhjcg== +kind: Secret +metadata: + name: {{ template "apk-helm.resource.prefix" . }}-sts-shared-auth-key + namespace: {{ .Release.Namespace }} +type: Opaque diff --git a/helm-charts/templates/data-plane/gateway-components/common-controller/common-controller-web-server-service.yaml b/helm-charts/templates/data-plane/gateway-components/common-controller/common-controller-web-server-service.yaml new file mode 100644 index 000000000..a51c9d238 --- /dev/null +++ b/helm-charts/templates/data-plane/gateway-components/common-controller/common-controller-web-server-service.yaml @@ -0,0 +1,32 @@ +# Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +# +# WSO2 LLC. licenses this file to you 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. + +{{ if or .Values.wso2.apk.dp.enabled .Values.wso2.apk.cp.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "apk-helm.resource.prefix" . }}-common-controller-web-server-service + namespace : {{ .Release.Namespace }} +spec: + type: LoadBalancer + selector: +{{ include "apk-helm.pod.selectorLabels" (dict "root" . "app" "commoncontroller" ) | indent 4}} + ports: + - name: web-server + protocol: TCP + port: 9543 + targetPort: 9543 +{{- end -}} diff --git a/helm-charts/templates/data-plane/gateway-components/common-log-conf.yaml b/helm-charts/templates/data-plane/gateway-components/common-log-conf.yaml index 64c9fe76b..a066b5059 100644 --- a/helm-charts/templates/data-plane/gateway-components/common-log-conf.yaml +++ b/helm-charts/templates/data-plane/gateway-components/common-log-conf.yaml @@ -22,4 +22,34 @@ data: [commoncontroller.truststore] location = "/home/wso2/security/truststore" + + [commoncontroller.redis] + {{- if .Values.wso2.apk.dp.commonController.deployment.redis }} + host = "{{ .Values.wso2.apk.dp.commonController.deployment.redis.host | default "redis-master" }}" + port = "{{ .Values.wso2.apk.dp.commonController.deployment.redis.port | default "6379" }}" + username = "{{ .Values.wso2.apk.dp.commonController.deployment.redis.username | default "default" }}" + password = "{{ .Values.wso2.apk.dp.commonController.deployment.redis.password | default "" }}" + userCertPath = "{{ .Values.wso2.apk.dp.commonController.deployment.redis.userCertPath | default "/home/wso2/security/keystore/commoncontroller.crt" }}" + userKeyPath = "{{ .Values.wso2.apk.dp.commonController.deployment.redis.userKeyPath | default "/home/wso2/security/keystore/commoncontroller.key" }}" + cACertPath = "{{ .Values.wso2.apk.dp.commonController.deployment.redis.redisCaCertPath | default "/home/wso2/security/keystore/commoncontroller.crt" }}" + tlsEnabled = {{ .Values.wso2.apk.dp.commonController.deployment.redis.tlsEnabled | default false }} + revokedTokenChannel = "{{ .Values.wso2.apk.dp.commonController.deployment.redis.channelName | default "wso2-apk-revoked-tokens-channel" }}" + {{- else }} + host = "redis-master" + port = "6379" + username = "default" + password = "" + userCertPath = "/home/wso2/security/keystore/commoncontroller.crt" + userKeyPath = "/home/wso2/security/keystore/commoncontroller.key" + cACertPath = "/home/wso2/security/keystore/commoncontroller.crt" + tlsEnabled = false + revokedTokenChannel = "wso2-apk-revoked-tokens-channel" + {{- end }} + [commoncontroller.sts] + authKeyPath = "/home/wso2/security/sts/auth_key.txt" + authKeyHeader = "stsAuthKey" + + [commoncontroller.webServer] + port = 9543 + {{- end -}} diff --git a/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-runtime-deployment.yaml b/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-runtime-deployment.yaml index 92185aa21..60bd1c693 100644 --- a/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-runtime-deployment.yaml +++ b/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-runtime-deployment.yaml @@ -58,6 +58,8 @@ spec: protocol: "TCP" - containerPort: 9001 protocol: "TCP" + - containerPort: 5006 + protocol: "TCP" - containerPort: 8084 protocol: "TCP" - containerPort: 9092 @@ -89,7 +91,62 @@ spec: - name: enforcer_admin_pwd value: admin - name: JAVA_OPTS - value: -Dhttpclient.hostnameVerifier=AllowAll -Xms512m -Xmx512m -XX:MaxRAMFraction=2 + value: -Dhttpclient.hostnameVerifier=AllowAll -Xms512m -Xmx512m -XX:MaxRAMFraction=2 + {{- if .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis }} + - name: REDIS_USERNAME + value: {{ .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis.username | default "default" }} + - name: REDIS_PASSWORD + value: {{ .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis.password | default "" }} + - name: REDIS_HOST + value: {{ .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis.host | default "redis-master" }} + - name: REDIS_PORT + value: {{ .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis.port | default "6379" }} + - name: IS_REDIS_TLS_ENABLED + value: {{ .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis.tlsEnabled | default "false" }} + - name: REDIS_REVOKED_TOKENS_CHANNEL + value: {{ .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis.channelName | default "wso2-apk-revoked-tokens-channel" }} + - name: REDIS_KEY_FILE + value: {{ .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis.userKeyPath | default "/home/wso2/security/redis/redis.key" }} + - name: REDIS_CERT_FILE + value: {{ .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis.userCertPath | default "/home/wso2/security/redis/redis.crt" }} + - name: REDIS_CA_CERT_FILE + value: {{ .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis.redisCaCertPath | default "/home/wso2/security/redis/redis-ca.key" }} + - name: REVOKED_TOKEN_CLEANUP_INTERVAL + value: {{ .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis.tokenCleanupInterval | default "3600" }} + {{- else }} + - name: REDIS_USERNAME + value: "default" + - name: REDIS_PASSWORD + value: "" + - name: REDIS_HOST + value: "redis-master" + - name: REDIS_PORT + value: "6379" + - name: IS_REDIS_TLS_ENABLED + value: "false" + - name: REDIS_REVOKED_TOKENS_CHANNEL + value: "wso2-apk-revoked-tokens-channel" + - name: REDIS_KEY_FILE + value: "/home/wso2/security/redis/redis.key" + - name: REDIS_CERT_FILE + value: "/home/wso2/security/redis/redis.crt" + - name: REDIS_CA_CERT_FILE + value: "/home/wso2/security/redis/redis-ca.key" + - name: REVOKED_TOKEN_CLEANUP_INTERVAL + value: "3600" + {{- end }} + {{- if and .Values.wso2.apk.dp.gatewayRuntime.analytics .Values.wso2.apk.dp.gatewayRuntime.analytics.secretName }} + - name: analytics_authToken + valueFrom: + secretKeyRef: + name: {{ .Values.wso2.apk.dp.gatewayRuntime.analytics.secretName }} + key: "analytics_authToken" + - name: analytics_authURL + valueFrom: + secretKeyRef: + name: {{ .Values.wso2.apk.dp.gatewayRuntime.analytics.secretName }} + key: "analytics_authURL" + {{- end }} volumeMounts: - name: tmp mountPath: /tmp diff --git a/helm-charts/values.yaml.template b/helm-charts/values.yaml.template index a02b6538c..3ad58a31e 100644 --- a/helm-charts/values.yaml.template +++ b/helm-charts/values.yaml.template @@ -450,6 +450,25 @@ wso2: # -- Optionally configure namespaces to watch for apis,ratelimitpolicies,etc. apiNamespaces: - "apk-v12" + redis: + # -- Redis host + host: "redis-master" + # -- Redis port + port: "6379" + # -- Redis user name + username: "default" + # -- Redis password + password: "" + # -- Redis TLS enabled or not + tlsEnabled: false + # -- Redis user cert to use for redis connections + userCertPath: "/home/wso2/security/keystore/commoncontroller.crt" + # -- Redis user key to use for redis connections + userKeyPath: "/home/wso2/security/keystore/commoncontroller.key" + # -- Redis CA cert to use for redis connections + cACertPath: "/home/wso2/security/keystore/commoncontroller.crt" + # -- Token revocation subscription channel name + channelName: "wso2-apk-revoked-tokens-channel" ratelimiter: # -- Enable the deployment of the Rate Limiter enabled: true @@ -633,6 +652,25 @@ wso2: level: DEBUG # -- Log file name logFile: logs/enforcer.log + redis: + # -- Redis host + host: "redis-master" + # -- Redis port + port: "6379" + # -- Redis user name + username: "default" + # -- Redis password + password: "" + # -- Redis user cert to use for redis connections + # -- Redis TLS enabled or not + tlsEnabled: false + userCertPath: "/home/wso2/security/keystore/commoncontroller.crt" + # -- Redis user key to use for redis connections + userKeyPath: "/home/wso2/security/keystore/commoncontroller.key" + # -- Redis CA cert to use for redis connections + cACertPath: "/home/wso2/security/keystore/commoncontroller.crt" + # -- Token revocation subscription channel name + channelName: "wso2-apk-revoked-tokens-channel" # Tracing configurations for gateway runtime tracing: # -- Enable/Disable tracing in gateway runtime. diff --git a/libs.versions.toml b/libs.versions.toml index ab47f1cb0..e414a93e3 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -100,6 +100,7 @@ moesif = {module = "com.moesif.api:moesifapi", version.ref = "moesif-api"} feign-httpclient = {module = "io.github.openfeign:feign-httpclient", version.ref = "io-github-openfeign"} ua-parser={module = "ua.parser.wso2:ua-parser", version.ref = "ua-parser"} javax-xml-bind = {module = "javax.xml.bind:jaxb-api", version.ref ="javax-xml-bind"} +jedis = {module = "redis.clients:jedis", version.ref = "jedis"} #Added as direct dependency for transitive dependency version upgrades reactor-netty-http = {module = "io.projectreactor.netty:reactor-netty-http", version.ref = "reactor-netty-http"} protobuf-java = {module = "com.google.protobuf:protobuf-java", version.ref = "protobuf-java"} @@ -193,4 +194,5 @@ moesif-api = "1.6.17" ua-parser = "1.5.4.wso2v2" jakarta-xml-apis = "4.0.1" javax-xml-bind = "2.3.1" -mock-server-netty = "3.10.8" \ No newline at end of file +mock-server-netty = "3.10.8" +jedis = "4.3.1" diff --git a/test/cucumber-tests/scripts/setup-hosts.sh b/test/cucumber-tests/scripts/setup-hosts.sh index 3e11766fc..4ebf9f1cd 100644 --- a/test/cucumber-tests/scripts/setup-hosts.sh +++ b/test/cucumber-tests/scripts/setup-hosts.sh @@ -8,8 +8,10 @@ kubectl wait deployment/interceptor-service-deployment -n apk-integration-test - kubectl wait --timeout=5m -n apk-integration-test deployment/apk-test-setup-wso2-apk-adapter-deployment --for=condition=Available kubectl wait --timeout=15m -n apk-integration-test deployment/apk-test-setup-wso2-apk-gateway-runtime-deployment --for=condition=Available IP=$(kubectl get svc apk-test-setup-wso2-apk-gateway-service -n apk-integration-test --output jsonpath='{.status.loadBalancer.ingress[0].ip}') +CC_IP=$(kubectl get svc apk-test-setup-wso2-apk-common-controller-web-server-service -n apk-integration-test --output jsonpath='{.status.loadBalancer.ingress[0].ip}') sudo echo "$IP localhost" | sudo tee -a /etc/hosts sudo echo "$IP idp.am.wso2.com" | sudo tee -a /etc/hosts +sudo echo "$CC_IP apk-test-setup-wso2-apk-common-controller-service.apk-integration-test.svc" | sudo tee -a /etc/hosts sudo echo "$IP api.am.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP default.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP org1.gw.wso2.com" | sudo tee -a /etc/hosts diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java index c19592166..1ba9414be 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java @@ -118,6 +118,7 @@ public void theResponseStatusCodeShouldBe(int expectedStatusCode) throws IOExcep @Then("I send {string} request to {string} with body {string}") public void sendHttpRequest(String httpMethod, String url, String body) throws IOException { + body = Utils.resolveVariables(body, sharedContext.getValueStore()); if (sharedContext.getResponse() instanceof CloseableHttpResponse) { ((CloseableHttpResponse) sharedContext.getResponse()).close(); } @@ -141,6 +142,7 @@ public void sendHttpRequest(String httpMethod, String url, String body) throws I // It will send request using a new thread and forget about the response @Then("I send {string} async request to {string} with body {string}") public void sendAsyncHttpRequest(String httpMethod, String url, String body) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + String finalBody = Utils.resolveVariables(body, sharedContext.getValueStore()); if (sharedContext.getResponse() instanceof CloseableHttpResponse) { ((CloseableHttpResponse) sharedContext.getResponse()).close(); } @@ -150,11 +152,11 @@ public void sendAsyncHttpRequest(String httpMethod, String url, String body) thr if (CurlOption.HttpMethod.GET.toString().toLowerCase().equals(httpMethod.toLowerCase())) { simpleHTTPClient.doGet(url, sharedContext.getHeaders()); } else if (CurlOption.HttpMethod.POST.toString().toLowerCase().equals(httpMethod.toLowerCase())) { - simpleHTTPClient.doPost(url, sharedContext.getHeaders(), body, null); + simpleHTTPClient.doPost(url, sharedContext.getHeaders(), finalBody, null); } else if (CurlOption.HttpMethod.PUT.toString().toLowerCase().equals(httpMethod.toLowerCase())) { - simpleHTTPClient.doPut(url, sharedContext.getHeaders(), body, null); + simpleHTTPClient.doPut(url, sharedContext.getHeaders(), finalBody, null); } else if (CurlOption.HttpMethod.DELETE.toString().toLowerCase().equals(httpMethod.toLowerCase())) { - simpleHTTPClient.doPut(url, sharedContext.getHeaders(), body, null); + simpleHTTPClient.doPut(url, sharedContext.getHeaders(), finalBody, null); } else if (CurlOption.HttpMethod.OPTIONS.toString().toLowerCase().equals(httpMethod.toLowerCase())) { simpleHTTPClient.doOptions(url, sharedContext.getHeaders(), null, null); } diff --git a/test/cucumber-tests/src/test/resources/tests/api/RevokedToken.feature b/test/cucumber-tests/src/test/resources/tests/api/RevokedToken.feature new file mode 100644 index 000000000..419fa86a4 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/tests/api/RevokedToken.feature @@ -0,0 +1,35 @@ +Feature: Token revocation + Scenario: Testing token revocation + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/jwt_basic_conf.yaml" + And the definition file "artifacts/definitions/employees_api.json" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + |Authorization|bearer ${accessToken}| + And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-basic/3.14/employee/" with body "" + And I eventually receive 200 response code, not accepting + |429| + |401| + Then I set headers + |stsAuthKey|2jrmypak391zsqz974ugdddebf812ofx1b9t1oq27530ir02tc815eemrx435qvcp41ucgy7v5uuawzi4qcmjrx0k1zgox2s28cr| + And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-basic/3.14/employee/" with body "" + And the response status code should be 200 + # Revoke the token + And I send "POST" request to "https://apk-test-setup-wso2-apk-common-controller-service.apk-integration-test.svc:9543/notify?type=TOKEN_REVOCATION" with body "{\"token\": \"${accessToken}\"}" + And the response status code should be 200 + And I wait for 5 seconds + And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-basic/3.14/employee/" with body "" + And the response status code should be 401 + + + Scenario Outline: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "" + Then the response status code should be + + Examples: + | apiID | expectedStatusCode | + | jwt-basic-test | 202 |