diff --git a/.github/workflows/docker-build-publish.yaml b/.github/workflows/docker-build-publish.yaml index 1bd75dd..37820aa 100644 --- a/.github/workflows/docker-build-publish.yaml +++ b/.github/workflows/docker-build-publish.yaml @@ -110,7 +110,19 @@ jobs: go version rm -rf example go test -v -race -covermode=atomic -coverprofile=coverage.out ./... - go tool cover -html=coverage.out -o coverage.html + + # This action uploads coverage to Codecov. + # https://github.com/codecov/codecov-action + - + name: Upload coverage to Codecov + id: go-coverage + uses: codecov/codecov-action@v3 + with: + files: ./coverage.out + flags: unittests + fail_ci_if_error: true + verbose: true + version: "latest" # The Github action runs CIS Dockerfile benchmark against dockerfiles in repository (CIS 4.1, 4.2, 4.3, 4.6, 4.7, 4.9, 4.10) # https://github.com/sysdiglabs/benchmark-dockerfile diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..9c98c89 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,85 @@ +name: lint + +on: + # If any commit message in your push or the HEAD commit of your PR contains the strings + # [skip ci], [ci skip], [no ci], [skip actions], or [actions skip] + # workflows triggered on the push or pull_request events will be skipped. + # https://github.blog/changelog/2021-02-08-github-actions-skip-pull-request-and-push-workflows-with-skip-ci/ + push: + branches: [ master ] + # Publish semver tags as releases. + tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ] + # If any commit message in your push or the HEAD commit of your PR contains the strings + # [skip ci], [ci skip], [no ci], [skip actions], or [actions skip] + # workflows triggered on the push or pull_request events will be skipped. + # https://github.blog/changelog/2021-02-08-github-actions-skip-pull-request-and-push-workflows-with-skip-ci/ + pull_request: + branches: [ master ] + +env: + GOLANG_VERSION: ^1.19 + +jobs: + # This job runs golangci-lint and reports issues from linters. + # https://github.com/golangci/golangci-lint-action + golangci-lint: + name: golangci-lint + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Setup Golang + uses: actions/setup-go@v3 + with: + go-version: ${{ env.GOLANG_VERSION }} + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + # continue-on-error: true + with: + # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version + version: "latest" + # Optional: golangci-lint command line arguments. + args: --issues-exit-code=0 -c ./.golangci.yml + + # This job runs markdownlint and reports issues from linters. + # https://github.com/DavidAnson/markdownlint-cli2-action + markdownlint: + name: markdownlint + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: markdownlint + uses: DavidAnson/markdownlint-cli2-action@v7 + continue-on-error: true + with: + globs: | + README.md + CHANGELOG.md + docs/*.md + + CodeQL: + name: CodeQL + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + strategy: + fail-fast: false + matrix: + language: ['go'] + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.golangci.yml b/.golangci.yml index a35765f..66d2a91 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,31 @@ linters-settings: errcheck: - ignore: fmt:.*,glg:.* + ignore: "fmt:.*,github.com/kpango/glg:.*" issues: - exclude: - - Error return value of .(glg.(Info|Log|Debug|Warn|Error|Success|Fail|Print|Println|CustomLog)f?). is not checked + max-same-issues: 0 + # exclude: + # - Error return value of .(glg.(Info|Log|Debug|Warn|Error|Success|Fail|Print|Println|CustomLog)f?). is not checked +linters: + presets: + # - bugs + - comment + # - complexity + # - error + # - format + # - import + # - metalinter + - module + - performance + # - sql + # - style + # - test + - unused + disable: + - varcheck + - deadcode + - structcheck + - maligned + - dupword + - godox + - gomoddirectives + - godot diff --git a/.remarkrc b/.remarkrc deleted file mode 100644 index 674075b..0000000 --- a/.remarkrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": [ - "remark-preset-lint-consistent", - "remark-preset-lint-recommended", - ["remark-lint-list-item-indent", false], - ["remark-lint-list-item-bullet-indent", false], - ["remark-lint-list-item-content-indent", false] - ] -} diff --git a/Makefile b/Makefile index d588422..626ce8f 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,10 @@ GO_VERSION:=$(shell go version) .PHONY: all clean bench bench-all profile lint test contributors update install -all: clean install lint test bench +all: build + +build: + go build clean: go clean ./... @@ -36,7 +39,7 @@ deps: clean rm -rf vendor lint: - gometalinter --enable-all . | rg -v comment + golangci-lint run -c ./.golangci.yml test: clean init GO111MODULE=on go test --race -v ./... diff --git a/README.md b/README.md index 27b8e92..157267b 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,18 @@ ![logo](./images/logo.png) - + - [What is Garm](#what-is-garm) - [Use Case](#use-case) - - [Authorization](#authorization) - - [Docker](#docker) - - [Usage](#usage) + - [Authorization](#authorization) + - [Docker](#docker) + - [Usage](#usage) - [Contributor License Agreement](#contributor-license-agreement) - [About releases](#about-releases) + ## What is Garm @@ -51,8 +52,9 @@ Garm convert the K8s request to Athenz request based on the mapping rules in `co P.S. It is just a sample deployment solution above. Garm can work on any environment as long as it can access both the API server and the Athenz server. ### Docker -```shell -$ docker pull docker.io/athenz/garm + +```bash +docker pull docker.io/athenz/garm ``` ### Usage @@ -70,5 +72,5 @@ Note that only for contributions to the garm repository on the [GitHub](https:// ## About releases - Releases - - [![GitHub release (latest by date)](https://img.shields.io/github/v/release/AthenZ/garm?style=flat-square&label=Github%20version)](https://github.com/AthenZ/garm/releases/latest) - - [![Docker Image Version (tag latest)](https://img.shields.io/docker/v/athenz/garm/latest?style=flat-square&label=Docker%20version)](https://hub.docker.com/r/athenz/garm/tags) + - [![GitHub release (latest by date)](https://img.shields.io/github/v/release/AthenZ/garm?style=flat-square&label=Github%20version)](https://github.com/AthenZ/garm/releases/latest) + - [![Docker Image Version (tag latest)](https://img.shields.io/docker/v/athenz/garm/latest?style=flat-square&label=Docker%20version)](https://hub.docker.com/r/athenz/garm/tags) diff --git a/docs/config-detail.md b/docs/config-detail.md index 4960d69..637ad68 100644 --- a/docs/config-detail.md +++ b/docs/config-detail.md @@ -1,77 +1,80 @@ # Configuration Detail - + - [TLS](#tls) - [Athenz n-token](#athenz-n-token) - [Request filtering](#request-filtering) - [Admin domain](#admin-domain) +- [Service domains](#service-domains) - [Resource mapping](#resource-mapping) - [Optional API group and resource name control](#optional-api-group-and-resource-name-control) - [Mapping for non-resources or empty namespace](#mapping-for-non-resources-or-empty-namespace) -- [P.S.](#ps) +- [Appendix](#appendix) - + + + - ## TLS ![TLS](./assets/tls.png) - -### Related configuration +**Related configuration** + 1. For garm, `config.yaml` - ```yaml - server.tls.ca - server.tls.cert - server.tls.key - athenz.root_ca - ``` + ```yaml + server.tls.ca + server.tls.cert + server.tls.key + + athenz.root_ca + ``` + 1. For kube-apiserver, `authz.yaml` - ```yaml - # https://github.com/kubernetes/apiserver/blob/master/plugin/pkg/authorizer/webhook/webhook.go#L69 - clusters.cluster.certificate-authority - # https://github.com/kubernetes/apiserver/blob/master/plugin/pkg/authorizer/webhook/webhook.go#L76-L77 - users.user.client-certificate - users.user.client-key - ``` + ```yaml + # https://github.com/kubernetes/apiserver/blob/master/plugin/pkg/authorizer/webhook/webhook.go#L69 + clusters.cluster.certificate-authority + + # https://github.com/kubernetes/apiserver/blob/master/plugin/pkg/authorizer/webhook/webhook.go#L76-L77 + users.user.client-certificate + users.user.client-key + ``` + +**Note** - -#### Note - Garm uses the same server certificate for /authn and /authz. - If `server.tls.ca` is not set, garm will not verify the client certificate of kube-apiserver. --- - ## Athenz n-token - -### Related configuration +**Related configuration** + ```yaml athenz.auth_header athenz.token.* ``` - -#### Note +**Note** + - N-token is for identifying a service (i.e. garm) in Athenz. Athenz then use the pre-configurated policy to check whether the requested access is authenticated. - N-token is sent to Athenz on every authentication request on the HTTP header with name `athenz.auth_header`. - If `athenz.token.ntoken_path` is set ([Copper Argos](https://github.com/AthenZ/athenz/blob/master/docs/copper_argos_dev.md)), garm will use the n-token in the file directly. - - It is better to set `athenz.token.validate_token: true` in this case. -- If `athenz.token.ntoken_path` is NOT set, garm will handle the token generation and update automatically. - - As the token is signed by `athenz.token.private_key`, please make sure that the corresponding public key is configurated in Athenz with the same `athenz.token.key_version`. + - It is better to set `athenz.token.validate_token: true` in this case. + - If `athenz.token.ntoken_path` is NOT set, garm will handle the token generation and update automatically. + - As the token is signed by `athenz.token.private_key`, please make sure that the corresponding public key is configurated in Athenz with the same `athenz.token.key_version`. --- - ## Request filtering - -### Related configuration +**Related configuration** + ```yaml map_rule.tld.platform.black_list map_rule.tld.platform.white_list @@ -79,76 +82,71 @@ map_rule.tld.platform.white_list map_rule.tld.service_athenz_domains ``` - -#### Note +**Note** + - Garm can directly reject kube-apiserver requests without querying Athenz. - `in black_list AND NOT in white_list` => directly reject - Support wildcard `*` matching. --- - ## Admin domain - -### Related configuration +**Related configuration** + ```yaml map_rule.tld.platform.admin_access_list map_rule.tld.platform.admin_athenz_domain ``` - -#### Note +**Note** + - Garm can map kube-apiserver requests using a separate admin domain in Athenz. - If the request matches any rules in `map_rule.tld.platform.admin_access_list`, garm will use `map_rule.tld.platform.admin_athenz_domain`. - Garm will send 1 more request than the number of `map_rule.tld.service_athenz_domains` to Athenz. The kube-apiserver request is allowed if any 1 is allowed in Athenz (OR logic). - If `service_domain_a` and `service_domain_b` are specified in `map_rule.tld.service_athenz_domains`, it is requested 3 times. - 1. Athenz resource **with** `service_domain_a` (One of those specified in `map_rule.tld.service_athenz_domains`) - 1. Athenz resource **with** `service_domain_b` (One of those specified in `map_rule.tld.service_athenz_domains`) - 1. Athenz resource **without** `map_rule.tld.service_athenz_domains` + 1. Athenz resource **with** `service_domain_a` (One of those specified in `map_rule.tld.service_athenz_domains`) + 1. Athenz resource **with** `service_domain_b` (One of those specified in `map_rule.tld.service_athenz_domains`) + 1. Athenz resource **without** `map_rule.tld.service_athenz_domains` --- - ## Service domains - -### Related configuration +**Related configuration** + ```yaml map_rule.tld.service_athenz_domains ``` - -#### Note +**Note** + - If the request not matches any rules in `map_rule.tld.platform.admin_access_list`, garm will use `map_rule.tld.service_athenz_domains`. - Garm will send request number of `map_rule.tld.service_athenz_domains` to Athenz. The kube-apiserver request is allowed if any 1 is allowed in Athenz (OR logic). - If `service_domain_a` and `service_domain_b` are specified, garm will be requested twice. --- - - ## Resource mapping - -### Related configuration +**Related configuration** + ```yaml map_rule.tld.platform.resource_mappings map_rule.tld.platform.verb_mappings ``` - -#### Note +**Note** + - Garm can map k8s resource to Athenz resource. - `spec.resourceAttributes.subresource` is appended to `spec.resourceAttributes.resource` before mapping as `spec.resourceAttributes.resource` with format `${resource}.${subresource}`. --- - ## Optional API group and resource name control - -### Related configuration +**Related configuration** + ```yaml map_rule.tld.platform.api_group_control map_rule.tld.platform.api_group_mappings @@ -157,17 +155,16 @@ map_rule.tld.platform.resource_name_control map_rule.tld.platform.resource_name_mappings ``` - -#### Note +**Note** + - Garm will only map `spec.resourceAttributes.group` and `spec.resourceAttributes.name` in kube-apiserver request body when `map_rule.tld.platform.*_control` is `true`. Else, they will be treated as `""` during mapping. --- - ## Mapping for non-resources or empty namespace - -### Related configuration +**Related configuration** + ```yaml map_rule.tld.platform.empty_namespace @@ -175,16 +172,16 @@ map_rule.tld.platform.non_resource_api_group map_rule.tld.platform.non_resource_namespace ``` - -#### Note +**Note** + - Garm can substitute empty or missing value from kube-apiserver request with above configuration. - In case of non-resource, resource is equal to `spec.non-resource-attributes.path`. --- - -## P.S. +## Appendix + - Above resources, - - `k8s resource`: ([refer](https://github.com/kubernetes/apiserver/blob/master/plugin/pkg/authorizer/webhook/webhook.go#L165)) - - `resource`: a variable inside garm - - `Athenz resource`: resource inside policy + - `k8s resource`: ([refer](https://github.com/kubernetes/apiserver/blob/master/plugin/pkg/authorizer/webhook/webhook.go#L165)) + - `resource`: a variable inside garm + - `Athenz resource`: resource inside policy diff --git a/docs/garm-functional-overview.md b/docs/garm-functional-overview.md index 26befce..1f6e8c2 100644 --- a/docs/garm-functional-overview.md +++ b/docs/garm-functional-overview.md @@ -2,123 +2,116 @@ ![flowchart](./assets/garm-functional-flowchart.png) - + -- [Garm Functional Overview](#garm-functional-overview) - - [Parse k8s resources](#parse-k8s-resources) - - [Map resources](#map-resources) - - [Subsitute Athenz domain & principal](#subsitute-athenz-domain--principal) - - [Filter k8s request](#filter-k8s-request) - - [Select Athenz domain](#select-athenz-domain) - - [Create Athenz assertion](#create-athenz-assertion) - - [P.S. during mapping](#ps-during-mapping) +- [Parse k8s resources](#parse-k8s-resources) +- [Map resources](#map-resources) +- [Subsitute Athenz domain \& principal](#subsitute-athenz-domain--principal) +- [Filter k8s request](#filter-k8s-request) +- [Select Athenz domain](#select-athenz-domain) +- [Create Athenz assertion](#create-athenz-assertion) + - [during mapping](#during-mapping) - + + - ## Parse k8s resources ![parse k8s resources](./assets/parse-k8s-resources.png) 1. [K8s authorization attributes](https://kubernetes.io/docs/reference/access-authn-authz/webhook/) - 1. ResourceAttributes - - [ResourceAttributes webhook.go](https://github.com/kubernetes/apiserver/blob/master/plugin/pkg/authorizer/webhook/webhook.go#L160-L168) - - [ResourceAttributes struct](https://github.com/stefanprodan/kubectl-kubesec/blob/master/vendor/k8s.io/api/authorization/v1beta1/types.go#L86-L112) - 1. NonResourceAttributes - - [NonResourceAttributes webhook.go](https://github.com/kubernetes/apiserver/blob/master/plugin/pkg/authorizer/webhook/webhook.go#L170-L173) - - [NonResourceAttributes struct](https://github.com/stefanprodan/kubectl-kubesec/blob/master/vendor/k8s.io/api/authorization/v1beta1/types.go#L114-L122) + 1. ResourceAttributes + - [ResourceAttributes webhook.go](https://github.com/kubernetes/apiserver/blob/master/plugin/pkg/authorizer/webhook/webhook.go#L160-L168) + - [ResourceAttributes struct](https://github.com/stefanprodan/kubectl-kubesec/blob/master/vendor/k8s.io/api/authorization/v1beta1/types.go#L86-L112) + 1. NonResourceAttributes + - [NonResourceAttributes webhook.go](https://github.com/kubernetes/apiserver/blob/master/plugin/pkg/authorizer/webhook/webhook.go#L170-L173) + - [NonResourceAttributes struct](https://github.com/stefanprodan/kubectl-kubesec/blob/master/vendor/k8s.io/api/authorization/v1beta1/types.go#L114-L122) 1. garm resource attributes - 1. `var namespace, verb, group, resource, name string` + 1. `var namespace, verb, group, resource, name string` - ## Map resources - verb - - `config.yaml`, `map_rule.tld.platform.verb_mappings` - - key-value mapping + - `config.yaml`, `map_rule.tld.platform.verb_mappings` + - key-value mapping - resource - - `config.yaml`, `map_rule.tld.platform.resource_mappings` - - key-value mapping + - `config.yaml`, `map_rule.tld.platform.resource_mappings` + - key-value mapping - group - - is `""` if `map_rule.tld.platform.api_group_control == false` - - `config.yaml`, `map_rule.tld.platform.api_group_mappings` - - key-value mapping + - is `""` if `map_rule.tld.platform.api_group_control == false` + - `config.yaml`, `map_rule.tld.platform.api_group_mappings` + - key-value mapping - name - - is `""` if `map_rule.tld.platform.resource_name_control == false` - - `config.yaml`, `map_rule.tld.platform.resource_name_mappings` - - key-value mapping + - is `""` if `map_rule.tld.platform.resource_name_control == false` + - `config.yaml`, `map_rule.tld.platform.resource_name_mappings` + - key-value mapping - ## Subsitute Athenz domain & principal - Map env. variable in Athenz service domain - - expectation - 1. split by `.` - 1. for each token matches `_.*_`, subsitute with env. variable (except `_namespace_`) - - example - - `_k8s_cluster_._namespace_.athenz.service.domain` => `SANDBOX._namespace_.athenz.service.domain` - + `config.GetActualValue("k8s_cluster") == "SANDBOX"` + - expectation + 1. split by `.` + 1. for each token matches `_.*_`, subsitute with env. variable (except `_namespace_`) + - example + - `_k8s_cluster_._namespace_.athenz.service.domain` => `SANDBOX._namespace_.athenz.service.domain` + `config.GetActualValue("k8s_cluster") == "SANDBOX"` - Map namespace in Athenz admain (both admin & service domain) - - expectation - 1. subsitute `_namespace_` string in `map_rule.tld.platform.admin_athenz_domain` with garm resource attributes `namespace` - - example - - `athenz.domain._namespace_` => `athenz.domain.kaas_namespace` - + `namespace = kaas_namespace` + - expectation + 1. subsitute `_namespace_` string in `map_rule.tld.platform.admin_athenz_domain` with garm resource attributes `namespace` + - example + - `athenz.domain._namespace_` => `athenz.domain.kaas_namespace` + `namespace = kaas_namespace` - Map k8s user to Athenz principal - - expectation - 1. remove `service_account_prefixes` - 1. subsitute namespace - 1. subsitute `:` - 1. if service account, prepend `athenz_service_account_prefix` - 1. if not service account, prepend `athenz_user_prefix` - - example - - `service_a:_namespace_:k8s_user` => `domain_a.k8s.kaas_namespace.k8s_user` - + `service_account_prefixes = []string{"service_a"}` - + `athenz_service_account_prefix = "domain_a.k8s."` - + `namespace = kaas_namespace` - - `service_b:service_c:k8s_user` => `domain_b.serviceaccount.service_c.k8s_user` - + `service_account_prefixes = []string{"service_b", "service_c"}` - + `athenz_service_account_prefix = "domain_b.k8s."` - - `service_b:k8s_user` => `domain_c.k8s.k8s_user` - + `service_account_prefixes = []string{"service_a", "service_b"}` - + `athenz_service_account_prefix = "domain_c.k8s."` - - `k8s_user` => `user.k8s_user` - + `athenz_user_prefix = "user."` + - expectation + 1. remove `service_account_prefixes` + 1. subsitute namespace + 1. subsitute `:` + 1. if service account, prepend `athenz_service_account_prefix` + 1. if not service account, prepend `athenz_user_prefix` + - example + - `service_a:_namespace_:k8s_user` => `domain_a.k8s.kaas_namespace.k8s_user` + - \+ `service_account_prefixes = []string{"service_a"}` + - \+ `athenz_service_account_prefix = "domain_a.k8s."` + - \+ `namespace = kaas_namespace` + - `service_b:service_c:k8s_user` => `domain_b.serviceaccount.service_c.k8s_user` + - \+ `service_account_prefixes = []string{"service_b", "service_c"}` + - \+ `athenz_service_account_prefix = "domain_b.k8s."` + - `service_b:k8s_user` => `domain_c.k8s.k8s_user` + - \+ `service_account_prefixes = []string{"service_a", "service_b"}` + - \+ `athenz_service_account_prefix = "domain_c.k8s."` + - `k8s_user` => `user.k8s_user` + - \+ `athenz_user_prefix = "user."` P.S. It may be easier to read the code directly. [createAthenzDomains()](../service/resolver.go#L110), [GetAdminDomain()](../service/resolver.go#280), [BuildDomainsFromNamespace()](../service/resolver.go#125), [PrincipalFromUser()](../service/resolver.go#L187) - ## Filter k8s request - `in black_list AND NOT in white_list` => directly reject - - `config.yaml`, `map_rule.tld.platform.black_list` & `map_rule.tld.platform.white_list` + - `config.yaml`, `map_rule.tld.platform.black_list` & `map_rule.tld.platform.white_list` - Matching logic - - create rule RegExp for matching - - ![garm resource matching](./assets/garm-resource-matching.png) - - Garm resource attribute is serialized before matching with the rule RegExp. + - create rule RegExp for matching + - ![garm resource matching](./assets/garm-resource-matching.png) + - Garm resource attribute is serialized before matching with the rule RegExp. - Example - - `RequestInfo{ Verb: "get", Namespace: "kube-system", APIGroup: "*", Resource: "secrets", Name: "alertmanager"}` => check with Athenz - - black_list contains `RequestInfo{ Verb: "*", Namespace: "kube-system", APIGroup: "*", Resource: "*", Name: "*"}`. - - white_list contains `RequestInfo{ Verb: "get", Namespace: "kube-system", APIGroup: "*", Resource: "secrets", Name: "alertmanager"}`. - - `RequestInfo{ Verb: "get", Namespace: "kube-system", APIGroup: "*", Resource: "secrets", Name: "my-secret"}` => directly reject - - black_list contains `RequestInfo{ Verb: "*", Namespace: "kube-system", APIGroup: "*", Resource: "*", Name: "*"}`. - - white_list **ONLY** contains `RequestInfo{ Verb: "get", Namespace: "kube-system", APIGroup: "*", Resource: "secrets", Name: "alertmanager"}`. - - + - `RequestInfo{ Verb: "get", Namespace: "kube-system", APIGroup: "*", Resource: "secrets", Name: "alertmanager"}` => check with Athenz + - black_list contains `RequestInfo{ Verb: "*", Namespace: "kube-system", APIGroup: "*", Resource: "*", Name: "*"}`. + - white_list contains `RequestInfo{ Verb: "get", Namespace: "kube-system", APIGroup: "*", Resource: "secrets", Name: "alertmanager"}`. + - `RequestInfo{ Verb: "get", Namespace: "kube-system", APIGroup: "*", Resource: "secrets", Name: "my-secret"}` => directly reject + - black_list contains `RequestInfo{ Verb: "*", Namespace: "kube-system", APIGroup: "*", Resource: "*", Name: "*"}`. + - white_list **ONLY** contains `RequestInfo{ Verb: "get", Namespace: "kube-system", APIGroup: "*", Resource: "secrets", Name: "alertmanager"}`. + ## Select Athenz domain + - `in admin_access_list` => use admin domain - - `config.yaml`, `map_rule.tld.platform.admin_access_list` + - `config.yaml`, `map_rule.tld.platform.admin_access_list` - Matching logic - - same as above + - same as above - ## Create Athenz assertion -###### P.S. during mapping +### during mapping + ![optional api group and resource name](./assets/optional-api-group-and-resource-name.png) - Athenz service domain - - ![create athenz assertion on service domain](./assets/create-athenz-assertion-on-service-domain.png) + - ![create athenz assertion on service domain](./assets/create-athenz-assertion-on-service-domain.png) - Athenz admin domain (2 requests to Athenz, OR logic, any one is allowed implies the action is allowed.) - - ![create athenz assertion on admin domain](./assets/create-athenz-assertion-on-admin-domain.png) - + - ![create athenz assertion on admin domain](./assets/create-athenz-assertion-on-admin-domain.png) diff --git a/docs/graceful-shutdown.md b/docs/graceful-shutdown.md index 88dc3f0..e11245c 100644 --- a/docs/graceful-shutdown.md +++ b/docs/graceful-shutdown.md @@ -1,3 +1,3 @@ # graceful shutdown -reference: https://github.com/AthenZ/authorization-proxy/blob/master/docs/graceful-shutdown.md +reference: diff --git a/docs/installation/01. setup-nodes.md b/docs/installation/01. setup-nodes.md index e2f30cf..bdf7e9c 100644 --- a/docs/installation/01. setup-nodes.md +++ b/docs/installation/01. setup-nodes.md @@ -1,19 +1,19 @@ # Setup k8s nodes - + -- [Setup k8s nodes](#setup-k8s-nodes) - - [Prepare linux env. for installing k8s](#prepare-linux-env-for-installing-k8s) - - [Install docker and k8s](#install-docker-and-k8s) - - [[master only] Config master node](#master-only-config-master-node) - - [Config the cluster](#config-the-cluster) - - [Join the cluster](#join-the-cluster) - - [Config local machine (host to remote control k8s)](#config-local-machine-host-to-remote-control-k8s) +- [Prepare linux env. for installing k8s](#prepare-linux-env-for-installing-k8s) +- [Install docker and k8s](#install-docker-and-k8s) +- [\[master only\] Config master node](#master-only-config-master-node) +- [Config the cluster](#config-the-cluster) +- [Join the cluster](#join-the-cluster) +- [Config local machine (host to remote control k8s)](#config-local-machine-host-to-remote-control-k8s) - + + - ## Prepare linux env. for installing k8s + ```bash #!/bin/sh sudo systemctl stop firewalld @@ -54,8 +54,8 @@ sudo grub2-mkconfig -o /boot/grub2/grub.cfg sudo reboot ``` - ## Install docker and k8s + ```bash #!/bin/sh sudo yum remove -y docker docker-common docker-selinux docker-engine kubelet kubeadm kubectl @@ -108,8 +108,8 @@ sudo /sbin/sysctl -w net.ipv4.ip_forward=1 sudo kubeadm reset ``` - ## [master only] Config master node + ```bash sudo kubeadm config images pull sudo kubeadm init --pod-network-cidr=192.168.0.0/16 @@ -138,8 +138,8 @@ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config ``` - ## Config the cluster + ```bash # install essential k8s services to cluster # wait for all k8s component completely started, else the following commands will fail @@ -147,15 +147,15 @@ kubectl apply -f https://docs.projectcalico.org/v3.1/getting-started/kubernetes/ kubectl apply -f https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml ``` - ## Join the cluster + ```bash # run the `kubeadm join` command on nodes kubeadm join {ip}:6443 --token {token} --discovery-token-ca-cert-hash {hash} ``` - ## Config local machine (host to remote control k8s) + ```bash # paste the content of `/etc/kubernetes/admin.conf` from master node cat > ~/.kube/config diff --git a/docs/installation/02. install-garm.md b/docs/installation/02. install-garm.md index ea04664..9b01956 100644 --- a/docs/installation/02. install-garm.md +++ b/docs/installation/02. install-garm.md @@ -1,24 +1,27 @@ # Install garm - + - [Prepare garm configuration](#prepare-garm-configuration) - - [Examples](#examples) + - [Examples](#examples) - [Prepare domain and service](#prepare-domain-and-service) - [Prepare Athenz and garm certificate](#prepare-athenz-and-garm-certificate) - - [Get root CA certificate and save as "Athenz root CA certificate"](#get-root-ca-certificate-and-save-as-athenz-root-ca-certificate) - - [Create garm certificate](#create-garm-certificate) - - [Add garm certificates as k8s secret](#add-garm-certificates-as-k8s-secret) + - [Get root CA certificate and save as "Athenz root CA certificate"](#get-root-ca-certificate-and-save-as-athenz-root-ca-certificate) + - [Create garm certificate](#create-garm-certificate) + - [Add garm certificates as k8s secret](#add-garm-certificates-as-k8s-secret) - [Apply the garm configuration](#apply-the-garm-configuration) - + + - ## Prepare garm configuration + Below files are under [/k8s/\*.yaml](../../k8s) ### Examples + 1. [garm-config.yaml](../../k8s/garm-config.yaml) + ```yaml token: ... (Omitted) ... @@ -41,7 +44,9 @@ Below files are under [/k8s/\*.yaml](../../k8s) metadata: ... (Omitted) ... ``` + 1. [garm-extapi.yaml](../../k8s/garm-extapi.yaml) + ```yaml apiVersion: v1 data: @@ -50,31 +55,35 @@ Below files are under [/k8s/\*.yaml](../../k8s) athenz-domain: athenz.garm.user service-name: garm-service ``` -1. [deployments.yaml](../../k8s/deployments.yaml) +1. [deployments.yaml](../../k8s/deployments.yaml) - comment out configurations not in use - ```yaml - spec: - template: + + ```yaml spec: - containers: - - env: - ... (Omitted) ... + template: + spec: + containers: + - env: + ... (Omitted) ... - # - name: ca - # valueFrom: - # configMapKeyRef: - # key: ca-public-key - # name: garm-extapi + # - name: ca + # valueFrom: + # configMapKeyRef: + # key: ca-public-key + # name: garm-extapi + + ... (Omitted) ... + ``` - ... (Omitted) ... - ``` - modify Docker Registry URL - ```yaml - image: docker.io/athenz/garm:latest - ``` + + ```yaml + image: docker.io/athenz/garm:latest + ``` 1. [service.yaml](../../k8s/service.yaml) + ```yaml # please make sure the IP works in your k8s cluster # reminder this IP, it will be used later @@ -82,9 +91,10 @@ Below files are under [/k8s/\*.yaml](../../k8s) clusterIP: 10.96.0.11 ``` - ## Prepare domain and service + 1. Create athenz domain + ```bash # sample domains @@ -95,24 +105,25 @@ Below files are under [/k8s/\*.yaml](../../k8s) # garm sub-domain for k8s user operation athenz.garm.user ``` + 1. Create service 1. Generate key pair for the service and register public key to Athenz 1. Save the private key as `athenz.key` - ## Prepare Athenz and garm certificate - + ### Get root CA certificate and save as "Athenz root CA certificate" Root CA depends on the Athenz server which Garm connects to. For example: -``` + +```bash # Cybertrust wget 'https://www.cybertrust.ne.jp/sureserver/download/root_ca/BCTRoot.txt' -O ./athenz_root_ca.key # DigiCert wget 'https://dl.cacerts.digicert.com/DigiCertHighAssuranceEVRootCA.crt' -O ./athenz_root_ca.key ``` - ### Create garm certificate + ```bash # CA openssl genrsa -out rootCA.key 4096 @@ -141,8 +152,8 @@ openssl x509 -in garm.crt -text -noout # openssl base64 -in garm.crt | tr -d '\n'; echo '' ``` - ### Add garm certificates as k8s secret + ```bash # private key for login athenz ATHENZ_PRIVATE_KEY='./athenz.key' @@ -160,8 +171,8 @@ kubectl create secret generic garm-secret-ca -n kube-public \ --from-file=garm-server-cert.pem="${GARM_SERVER_CERT}" ``` - ## Apply the garm configuration + ```bash # please execute with the same order kubectl apply -f garm-extapi.yaml diff --git a/docs/installation/03. config-k8s-in-webhook-mode.md b/docs/installation/03. config-k8s-in-webhook-mode.md index 68d8d92..3f887d4 100644 --- a/docs/installation/03. config-k8s-in-webhook-mode.md +++ b/docs/installation/03. config-k8s-in-webhook-mode.md @@ -1,52 +1,52 @@ # Config k8s in webhook mode and use garm - + -- [Config k8s in webhook mode and use garm](#config-k8s-in-webhook-mode-and-use-garm) - - [Single master](#single-master) - - [Prepare required files](#prepare-required-files) - - [Config master node](#config-master-node) - - [Check garm can get webhook request](#check-garm-can-get-webhook-request) - - [Extra](#extra) - - [Using ConfigMap (single master will not work)](#using-configmap-single-master-will-not-work) - - [Add webhook user certificate as k8s secret](#add-webhook-user-certificate-as-k8s-secret) - - [[master node] Set apiserver to webhook mode (below config is not tested)](#master-node-set-apiserver-to-webhook-mode-below-config-is-not-tested) +- [Single master](#single-master) + - [Prepare required files](#prepare-required-files) + - [Config master node](#config-master-node) +- [Check garm can get webhook request](#check-garm-can-get-webhook-request) +- [Extra](#extra) + - [Using ConfigMap (single master will not work)](#using-configmap-single-master-will-not-work) + - [Add webhook user certificate as k8s secret](#add-webhook-user-certificate-as-k8s-secret) + - [\[master node\] Set apiserver to webhook mode (below config is not tested)](#master-node-set-apiserver-to-webhook-mode-below-config-is-not-tested) - + + - ## Single master - ### Prepare required files + - File from [previous tutorial](./02.%20install-garm.md) - - `user.key` - - `user.crt` - - `rootCA.crt` (rename to `garm-ca.crt`) + - `user.key` + - `user.crt` + - `rootCA.crt` (rename to `garm-ca.crt`) - `authz.yaml` - - update the IP address below (`10.96.0.11`) to garm service IP ([service.yaml](../../k8s/service.yaml)) - - sample file content - ```yaml - clusters: - - name: kubernetes - cluster: - certificate-authority: /etc/kubernetes/pki/garm-ca.crt - server: https://10.96.0.11/authz - users: - - name: my-api-server - user: - client-certificate: /etc/kubernetes/pki/user.crt - client-key: /etc/kubernetes/pki/user.key - current-context: webhook - contexts: - - context: - cluster: kubernetes - user: my-api-sever - name: webhook - ``` - - + - update the IP address below (`10.96.0.11`) to garm service IP ([service.yaml](../../k8s/service.yaml)) + - sample file content + + ```yaml + clusters: + - name: kubernetes + cluster: + certificate-authority: /etc/kubernetes/pki/garm-ca.crt + server: https://10.96.0.11/authz + users: + - name: my-api-server + user: + client-certificate: /etc/kubernetes/pki/user.crt + client-key: /etc/kubernetes/pki/user.key + current-context: webhook + contexts: + - context: + cluster: kubernetes + user: my-api-sever + name: webhook + ``` + ### Config master node + ```bash # move the requried files to apiserver mount point sudo mv *.key *.crt /etc/kubernetes/pki/ @@ -62,8 +62,8 @@ sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml # wait for apiserver to restart automatically ``` - ## Check garm can get webhook request + ```bash # create a dummy user kubectl config delete-context testing-garm @@ -81,11 +81,10 @@ kubectl config use-context kubernetes-admin@kubernetes kubectl logs garm-7bf5bc6f9d-b5l5c -n kube-public ``` - ## Extra - ### Using ConfigMap (single master will not work) + ```bash cat | kubectl apply -f - <<'EOF' apiVersion: v1 @@ -121,8 +120,8 @@ EOF kubectl get configmap authz-config -n kube-system -o yaml | less ``` - ### Add webhook user certificate as k8s secret + ```bash WEBHOOK_USER_KEY='./user.key' WEBHOOK_USER_CERT='./user.crt' @@ -143,9 +142,10 @@ kubectl get secret webhook-user -n kube-system -o yaml | less kubectl get secret garm-root-ca -n kube-system -o yaml | less ``` - #### [master node] Set apiserver to webhook mode (below config is not tested) + `sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml` + ```yaml - --authorization-mode=Node,RBAC,Webhook - --authorization-webhook-config-file=/etc/kubernetes/authz.yaml @@ -173,5 +173,4 @@ kubectl get secret garm-root-ca -n kube-system -o yaml | less secret: defaultMode: 420 secretName: garm-root-ca - ``` diff --git a/main.go b/main.go index 7c0903e..50daa59 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ import ( "github.com/pkg/errors" ) -// Version is set by the build command via LDFLAGS +// Version is set by the build command via LDFLAGS'. var Version string // params is the data model for Garm command line arguments. diff --git a/router/router_test.go b/router/router_test.go index 0f1e97f..feb11ba 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -35,10 +35,10 @@ import ( "github.com/kpango/glg" ) -// glgMutex prevent race condition on glg +// glgMutex prevent race condition on glg. var glgMutex = &sync.Mutex{} -// dummyHandler is a mock implement for handler.Handler +// dummyHandler is a mock implement for handler.Handler. type dummyHandler struct { responseValue string } @@ -118,7 +118,7 @@ func TestNewServeMux(t *testing.T) { want = "Authenticate response" handler.responseValue = want recorder = httptest.NewRecorder() - request, err = http.NewRequest(http.MethodPost, "/authn", nil) + request, err = http.NewRequestWithContext(context.Background(), http.MethodPost, "/authn", nil) if err != nil { return } @@ -137,7 +137,7 @@ func TestNewServeMux(t *testing.T) { want = "Authorize response" handler.responseValue = want recorder = httptest.NewRecorder() - request, err = http.NewRequest(http.MethodPost, "/authz", nil) + request, err = http.NewRequestWithContext(context.Background(), http.MethodPost, "/authz", nil) if err != nil { return } @@ -229,7 +229,7 @@ func Test_routing(t *testing.T) { h: handlerFunc, }, checkFunc: func(server http.Handler) error { - request, err := http.NewRequest(http.MethodGet, "/", nil) + request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) if err != nil { return err } @@ -273,7 +273,7 @@ func Test_routing(t *testing.T) { glgMutex.Lock() glg.Get().SetMode(glg.NONE) - request, err := http.NewRequest(http.MethodGet, "/", nil) + request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) if err != nil { return err } @@ -311,7 +311,7 @@ func Test_routing(t *testing.T) { h: handlerFunc, }, checkFunc: func(server http.Handler) error { - request, err := http.NewRequest(http.MethodGet, "/", nil) + request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) if err != nil { return err } @@ -351,7 +351,7 @@ func Test_routing(t *testing.T) { h: handlerFunc, }, checkFunc: func(server http.Handler) error { - request, err := http.NewRequest(http.MethodOptions, "/", nil) + request, err := http.NewRequestWithContext(context.Background(), http.MethodOptions, "/", nil) if err != nil { return err } @@ -393,7 +393,7 @@ func Test_routing(t *testing.T) { }, checkFunc: func(server http.Handler) error { for _, method := range []string{http.MethodGet, http.MethodPost} { - request, err := http.NewRequest(method, "/", nil) + request, err := http.NewRequestWithContext(context.Background(), method, "/", nil) if err != nil { return err } @@ -442,7 +442,7 @@ func Test_routing(t *testing.T) { errorBuffer := new(bytes.Buffer) glg.Get().SetMode(glg.WRITER).SetWriter(errorBuffer) - request, err := http.NewRequest(http.MethodGet, "/", nil) + request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) if err != nil { return err } @@ -486,7 +486,7 @@ func Test_routing(t *testing.T) { errorBuffer := new(bytes.Buffer) glg.Get().SetMode(glg.WRITER).SetWriter(errorBuffer) - request, err := http.NewRequest(http.MethodGet, "/", nil) + request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) if err != nil { return err } @@ -540,7 +540,7 @@ func Test_routing(t *testing.T) { rpr, rpw := io.Pipe() rpw.Close() rpr.Close() - request, err := http.NewRequest(http.MethodGet, "/", rpr) + request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", rpr) if err != nil { return err } @@ -585,7 +585,7 @@ func Test_routing(t *testing.T) { h: handlerFunc, }, checkFunc: func(server http.Handler) error { - request, err := http.NewRequest(http.MethodGet, "/", nil) + request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) if err != nil { return err } @@ -780,7 +780,7 @@ func Test_recoverWrap(t *testing.T) { h: handlerFunc, }, checkFunc: func(server http.Handler) error { - request, err := http.NewRequest(http.MethodGet, "/", nil) + request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) if err != nil { return err } @@ -825,7 +825,7 @@ func Test_recoverWrap(t *testing.T) { errorBuffer := new(bytes.Buffer) glg.Get().SetMode(glg.WRITER).SetWriter(errorBuffer) - request, err := http.NewRequest(http.MethodGet, "/", nil) + request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) if err != nil { return err } diff --git a/service/athenz.go b/service/athenz.go index 46f2780..31855fd 100755 --- a/service/athenz.go +++ b/service/athenz.go @@ -34,7 +34,7 @@ type Athenz interface { AthenzAuthenticator(http.ResponseWriter, *http.Request) error } -// Wrapper for Athenz HTTP request handlers +// Wrapper for Athenz HTTP request handlers. type athenz struct { // authConfig is the shared configuration for the HTTP handlers. authConfig config.Athenz diff --git a/service/athenz_test.go b/service/athenz_test.go index 8cad6be..cb0874b 100644 --- a/service/athenz_test.go +++ b/service/athenz_test.go @@ -35,19 +35,19 @@ import ( authz "k8s.io/api/authorization/v1beta1" ) -// dummyLogger is a mock implementation for webhook.Logger +// dummyLogger is a mock implementation for webhook.Logger. type dummyLogger string -// Println is mock method for webhook.Logger interface +// Println is mock method for webhook.Logger interface. func (dummyLogger) Println(args ...interface{}) {} -// Printf is mock method for webhook.Logger interface +// Printf is mock method for webhook.Logger interface. func (dummyLogger) Printf(format string, args ...interface{}) {} -// dummyLogger is a mock implementation for webhook.ResourceMapper and webhook.UserMapper +// dummyLogger is a mock implementation for webhook.ResourceMapper and webhook.UserMapper. type dummyMapper string -// MapResource is mock method for webhook.ResourceMapper interface +// MapResource is mock method for webhook.ResourceMapper interface. func (dummyMapper) MapResource(ctx context.Context, spec authz.SubjectAccessReviewSpec) (principal string, checks []webhook.AthenzAccessCheck, err error) { principal = "principal" checks = []webhook.AthenzAccessCheck{ @@ -64,7 +64,7 @@ func (dummyMapper) MapResource(ctx context.Context, spec authz.SubjectAccessRevi return } -// MapUser is mock method for webhook.UserMapper interface +// MapUser is mock method for webhook.UserMapper interface. func (dummyMapper) MapUser(ctx context.Context, domain, service string) (authn.UserInfo, error) { return authn.UserInfo{ Username: "username", diff --git a/service/resolver.go b/service/resolver.go index a101eb2..00d9bf2 100755 --- a/service/resolver.go +++ b/service/resolver.go @@ -127,7 +127,7 @@ func (r *resolve) MapK8sResourceAthenzResource(k8sRes string) string { // do the following for each athenzDomains // split it with "."; // for each token, if it match /^_.*_$/ but not "_namespace_", replace the token with config.GetActualValue(token); -// and then return the processed value +// and then return the processed value. func (r *resolve) createAthenzDomains(athenzDomains []string) []string { if len(athenzDomains) == 0 { return athenzDomains @@ -157,7 +157,8 @@ func (r *resolve) BuildDomainsFromNamespace(namespace string) []string { return r.buildAthenzDomain(r.athenzDomains, namespace) } -// BuildServiceAccountPrefixFromNamespace returns domains by processing AthenzServiceAccountPrefix. +// BuildServiceAccountPrefixFromNamespace returns domains by processing AthenzServiceAccountPrefix. +// // if namespace != "", replace `/ = .`, then `.. => -`, then replace "_namespace_" in AthenzServiceAccountPrefix with namespace; // else replace "._namespace_" in AthenzServiceAccountPrefix with namespace; // trim ".", then "-", then ":" @@ -223,17 +224,17 @@ func (r *resolve) MapResourceName(name string) string { return name } -// GetEmptyNamespace returns cfg.EmptyNamespace +// GetEmptyNamespace returns cfg.EmptyNamespace. func (r *resolve) GetEmptyNamespace() string { return r.cfg.EmptyNamespace } -// GetNonResourceGroup returns cfg.NonResourceAPIGroup +// GetNonResourceGroup returns cfg.NonResourceAPIGroup. func (r *resolve) GetNonResourceGroup() string { return r.cfg.NonResourceAPIGroup } -// GetNonResourceNamespace returns cfg.NonResourceNamespace +// GetNonResourceNamespace returns cfg.NonResourceNamespace. func (r *resolve) GetNonResourceNamespace() string { return r.cfg.NonResourceNamespace } @@ -339,7 +340,7 @@ func (r *resolve) IsAllowed(verb, namespace, apiGroup, resource, name string) bo return true } -// IsAdminAccess returns true, if any admin access in config match +// IsAdminAccess returns true, if any admin access in config match. func (r *resolve) IsAdminAccess(verb, namespace, apiGroup, resource, name string) bool { var ok bool for _, admin := range r.cfg.AdminAccessList { diff --git a/service/resolver_test.go b/service/resolver_test.go index 153f9e9..8e5f8c6 100644 --- a/service/resolver_test.go +++ b/service/resolver_test.go @@ -506,8 +506,9 @@ func Test_resolve_createAthenzDomains(t *testing.T) { } r := &resolve{ - cfg: tt.fields.cfg, - athenzDomains: tt.fields.athenzDomains, + cfg: tt.fields.cfg, + athenzDomains: tt.fields.athenzDomains, + athenzSAPrefix: tt.fields.athenzSAPrefix, } if got := r.createAthenzDomains(tt.args.athenzDomains); !reflect.DeepEqual(got, tt.want) { t.Errorf("resolve.createAthenzDomains() = %v, want %v", got, tt.want) diff --git a/service/server.go b/service/server.go index 3e11dd2..e169713 100755 --- a/service/server.go +++ b/service/server.go @@ -55,18 +55,18 @@ type server struct { } const ( - // ContentType represents a HTTP header name "Content-Type" + // ContentType represents a HTTP header name "Content-Type". ContentType = "Content-Type" - // TextPlain represents a HTTP content type "text/plain" + // TextPlain represents a HTTP content type "text/plain". TextPlain = "text/plain" - // CharsetUTF8 represents a UTF-8 charset for HTTP response "charset=UTF-8" + // CharsetUTF8 represents a UTF-8 charset for HTTP response "charset=UTF-8". CharsetUTF8 = "charset=UTF-8" ) var ( - // ErrContextClosed represents the error that the context is closed + // ErrContextClosed represents the error that the context is closed. ErrContextClosed = errors.New("context Closed") ) diff --git a/service/server_test.go b/service/server_test.go index e1a5a60..43e40d8 100644 --- a/service/server_test.go +++ b/service/server_test.go @@ -180,10 +180,15 @@ func Test_server_ListenAndServe(t *testing.T) { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} checkSrvRunning := func(addr string) error { - res, err := http.DefaultClient.Get(addr) + req, err := http.NewRequestWithContext(context.Background(), "GET", addr, nil) if err != nil { return err } + res, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer res.Body.Close() if res.StatusCode != 200 { return fmt.Errorf("Response status code invalid, %v", res.StatusCode) } @@ -284,10 +289,15 @@ func Test_server_ListenAndServe(t *testing.T) { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} checkSrvRunning := func(addr string) error { - res, err := http.DefaultClient.Get(addr) + req, err := http.NewRequestWithContext(context.Background(), "GET", addr, nil) + if err != nil { + return err + } + res, err := http.DefaultClient.Do(req) if err != nil { return err } + defer res.Body.Close() if res.StatusCode != 200 { return fmt.Errorf("Response status code invalid, %v", res.StatusCode) } @@ -388,10 +398,15 @@ func Test_server_ListenAndServe(t *testing.T) { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} checkSrvRunning := func(addr string) error { - res, err := http.DefaultClient.Get(addr) + req, err := http.NewRequestWithContext(context.Background(), "GET", addr, nil) if err != nil { return err } + res, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer res.Body.Close() if res.StatusCode != 200 { return fmt.Errorf("Response status code invalid, %v", res.StatusCode) } @@ -468,7 +483,6 @@ func Test_server_hcShutdown(t *testing.T) { type fields struct { srv *http.Server srvRunning bool - hcsrv *http.Server hcrunning bool cfg config.Server pwt time.Duration @@ -547,7 +561,6 @@ func Test_server_hcShutdown(t *testing.T) { func Test_server_apiShutdown(t *testing.T) { type fields struct { - srv *http.Server srvRunning bool hcsrv *http.Server hcrunning bool @@ -703,6 +716,7 @@ func Test_server_handleHealthCheckRequest(t *testing.T) { }, checkFunc: func() error { result := rw.Result() + defer result.Body.Close() if header := result.StatusCode; header != http.StatusOK { return fmt.Errorf("Header is not correct, got: %v", header) } diff --git a/service/token.go b/service/token.go index 23caba0..dde96a0 100755 --- a/service/token.go +++ b/service/token.go @@ -48,7 +48,7 @@ type token struct { } var ( - // ErrTokenNotFound represents the error that the token is not found + // ErrTokenNotFound represents the error that the token is not found. ErrTokenNotFound = errors.New("Error:\ttoken not found") )