diff --git a/.gitignore b/.gitignore index 656cb8c780..4bfb1742bd 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ cmd/sys-mgmt-agent/sys-mgmt-agent cmd/sys-mgmt-executor/sys-mgmt-executor cmd/security-bootstrap-redis/security-bootstrap-redis cmd/secrets-config/secrets-config +cmd/security-bootstrapper/security-bootstrapper docs/_build/ diff --git a/Makefile b/Makefile index baf814d0aa..427467ec97 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ DOCKERS= \ docker_support_scheduler \ docker_security_proxy_setup \ docker_security_secretstore_setup \ - docker_security_bootstrap_redis + docker_security_bootstrapper .PHONY: $(DOCKERS) @@ -35,7 +35,8 @@ MICROSERVICES= \ cmd/security-secretstore-setup/security-secretstore-setup \ cmd/security-file-token-provider/security-file-token-provider \ cmd/security-bootstrap-redis/security-bootstrap-redis \ - cmd/secrets-config/secrets-config + cmd/secrets-config/secrets-config \ + cmd/security-bootstrapper/security-bootstrapper .PHONY: $(MICROSERVICES) @@ -87,6 +88,9 @@ cmd/security-bootstrap-redis/security-bootstrap-redis: cmd/secrets-config/secrets-config: $(GO) build $(GOFLAGS) -o ./cmd/secrets-config ./cmd/secrets-config +cmd/security-bootstrapper/security-bootstrapper: + $(GO) build $(GOFLAGS) -o ./cmd/security-bootstrapper/security-bootstrapper ./cmd/security-bootstrapper + clean: rm -f $(MICROSERVICES) @@ -186,10 +190,12 @@ docker_security_secretstore_setup: -t edgexfoundry/docker-security-secretstore-setup-go:$(DOCKER_TAG) \ . -docker_security_bootstrap_redis: - docker build \ - -f cmd/security-bootstrap-redis/Dockerfile \ +docker_security_bootstrapper: + docker build \ + --build-arg http_proxy \ + --build-arg https_proxy \ + -f cmd/security-bootstrapper/Dockerfile \ --label "git_sha=$(GIT_SHA)" \ - -t edgexfoundry/docker-security-bootstrap-redis-go:$(GIT_SHA) \ - -t edgexfoundry/docker-security-bootstrap-redis-go:$(DOCKER_TAG) \ + -t edgexfoundry/docker-security-bootstrapper-go:$(GIT_SHA) \ + -t edgexfoundry/docker-security-bootstrapper-go:$(DOCKER_TAG) \ . diff --git a/cmd/core-command/Attribution.txt b/cmd/core-command/Attribution.txt index cc75ebfe39..b935fe0cd8 100644 --- a/cmd/core-command/Attribution.txt +++ b/cmd/core-command/Attribution.txt @@ -174,3 +174,6 @@ https://github.com/go-playground/validator/blob/master/LICENSE leodido/go-urn (MIT) https://github.com/leodido/go-urn https://github.com/leodido/go-urn + +github.com/lib/pq (MIT) https://github.com/lib/pq +https://github.com/lib/pq/blob/master/LICENSE.md diff --git a/cmd/core-command/Dockerfile b/cmd/core-command/Dockerfile index 071028a4d6..200fe5224c 100644 --- a/cmd/core-command/Dockerfile +++ b/cmd/core-command/Dockerfile @@ -36,10 +36,12 @@ COPY . . RUN make cmd/core-command/core-command -FROM scratch +FROM alpine:3.12 + +RUN apk add --update --no-cache ca-certificates dumb-init LABEL license='SPDX-License-Identifier: Apache-2.0' \ - copyright='Copyright (c) 2018: Dell, Cavium' + copyright='Copyright (c) 2018: Dell, Cavium, Copyright (c) 2021: Intel Corporation' ENV APP_PORT=48082 #expose command data port @@ -49,5 +51,13 @@ WORKDIR / COPY --from=builder /edgex-go/cmd/core-command/Attribution.txt / COPY --from=builder /edgex-go/cmd/core-command/core-command / COPY --from=builder /edgex-go/cmd/core-command/res/configuration.toml /res/configuration.toml -ENTRYPOINT ["/core-command"] -CMD ["-cp=consul.http://edgex-core-consul:8500", "--registry", "--confdir=/res"] + +ENV CONSUL_HOST=edgex-core-consul +ENV CONSUL_PORT=8500 + +# setup entrypoint script +COPY --from=builder /edgex-go/cmd/core-command/entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/entrypoint.sh \ + && ln -s /usr/local/bin/entrypoint.sh / + +ENTRYPOINT ["entrypoint.sh"] diff --git a/cmd/core-command/entrypoint.sh b/cmd/core-command/entrypoint.sh new file mode 100644 index 0000000000..80d791a621 --- /dev/null +++ b/cmd/core-command/entrypoint.sh @@ -0,0 +1,25 @@ +#!/usr/bin/dumb-init /bin/sh +# ---------------------------------------------------------------------------------- +# Copyright (c) 2021 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +set -e + +echo "CONSUL_HOST: ${CONSUL_HOST} CONSUL_PORT: $CONSUL_PORT" + +echo "$(date) Starting edgex-core-command..." +exec /core-command -cp=consul.http://"${CONSUL_HOST}":"$CONSUL_PORT" --registry --confdir=/res diff --git a/cmd/core-data/Attribution.txt b/cmd/core-data/Attribution.txt index 2fcd1ae4df..2660c53060 100644 --- a/cmd/core-data/Attribution.txt +++ b/cmd/core-data/Attribution.txt @@ -174,3 +174,6 @@ https://github.com/go-playground/validator/blob/master/LICENSE leodido/go-urn (MIT) https://github.com/leodido/go-urn https://github.com/leodido/go-urn + +github.com/lib/pq (MIT) https://github.com/lib/pq +https://github.com/lib/pq/blob/master/LICENSE.md diff --git a/cmd/core-data/Dockerfile b/cmd/core-data/Dockerfile index 848fe042c1..e13b675aca 100644 --- a/cmd/core-data/Dockerfile +++ b/cmd/core-data/Dockerfile @@ -50,10 +50,17 @@ EXPOSE $APP_PORT # So we can try these. RUN sed -e 's/dl-cdn[.]alpinelinux.org/nl.alpinelinux.org/g' -i~ /etc/apk/repositories -RUN apk add --update --no-cache zeromq +RUN apk add --update --no-cache zeromq dumb-init COPY --from=builder /edgex-go/cmd/core-data/Attribution.txt / COPY --from=builder /edgex-go/cmd/core-data/core-data / COPY --from=builder /edgex-go/cmd/core-data/res/configuration.toml /res/configuration.toml -ENTRYPOINT ["/core-data"] -CMD ["-cp=consul.http://edgex-core-consul:8500", "--registry", "--confdir=/res"] +ENV CONSUL_HOST=edgex-core-consul +ENV CONSUL_PORT=8500 + +# setup entrypoint script +COPY --from=builder /edgex-go/cmd/core-data/entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/entrypoint.sh \ + && ln -s /usr/local/bin/entrypoint.sh / + +ENTRYPOINT ["entrypoint.sh"] diff --git a/cmd/core-data/entrypoint.sh b/cmd/core-data/entrypoint.sh new file mode 100644 index 0000000000..2169ef1277 --- /dev/null +++ b/cmd/core-data/entrypoint.sh @@ -0,0 +1,25 @@ +#!/usr/bin/dumb-init /bin/sh +# ---------------------------------------------------------------------------------- +# Copyright (c) 2021 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +set -e + +echo "CONSUL_HOST: ${CONSUL_HOST} CONSUL_PORT: $CONSUL_PORT" + +echo "$(date) Starting edgex-core-data..." +exec /core-data -cp=consul.http://"${CONSUL_HOST}":"$CONSUL_PORT" --registry --confdir=/res diff --git a/cmd/core-metadata/Attribution.txt b/cmd/core-metadata/Attribution.txt index f17499436f..cc3a9d44fa 100644 --- a/cmd/core-metadata/Attribution.txt +++ b/cmd/core-metadata/Attribution.txt @@ -174,3 +174,6 @@ https://github.com/go-playground/validator/blob/master/LICENSE leodido/go-urn (MIT) https://github.com/leodido/go-urn https://github.com/leodido/go-urn + +github.com/lib/pq (MIT) https://github.com/lib/pq +https://github.com/lib/pq/blob/master/LICENSE.md diff --git a/cmd/core-metadata/Dockerfile b/cmd/core-metadata/Dockerfile index 20538832fa..bc3bbcb686 100644 --- a/cmd/core-metadata/Dockerfile +++ b/cmd/core-metadata/Dockerfile @@ -36,10 +36,12 @@ COPY . . RUN make cmd/core-metadata/core-metadata #Next image - Copy built Go binary into new workspace -FROM scratch +FROM alpine:3.12 + +RUN apk add --update --no-cache ca-certificates dumb-init LABEL license='SPDX-License-Identifier: Apache-2.0' \ - copyright='Copyright (c) 2018: Dell, Cavium' + copyright='Copyright (c) 2018: Dell, Cavium, Copyright (c) 2021: Intel Corporation' ENV APP_PORT=48081 #expose meta data port @@ -49,5 +51,13 @@ WORKDIR / COPY --from=builder /edgex-go/cmd/core-metadata/Attribution.txt / COPY --from=builder /edgex-go/cmd/core-metadata/core-metadata / COPY --from=builder /edgex-go/cmd/core-metadata/res/configuration.toml /res/configuration.toml -ENTRYPOINT ["/core-metadata"] -CMD ["-cp=consul.http://edgex-core-consul:8500", "--registry", "--confdir=/res"] + +ENV CONSUL_HOST=edgex-core-consul +ENV CONSUL_PORT=8500 + +# setup entrypoint script +COPY --from=builder /edgex-go/cmd/core-metadata/entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/entrypoint.sh \ + && ln -s /usr/local/bin/entrypoint.sh / + +ENTRYPOINT ["entrypoint.sh"] diff --git a/cmd/core-metadata/entrypoint.sh b/cmd/core-metadata/entrypoint.sh new file mode 100644 index 0000000000..ebadd207a8 --- /dev/null +++ b/cmd/core-metadata/entrypoint.sh @@ -0,0 +1,25 @@ +#!/usr/bin/dumb-init /bin/sh +# ---------------------------------------------------------------------------------- +# Copyright (c) 2021 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +set -e + +echo "CONSUL_HOST: ${CONSUL_HOST} CONSUL_PORT: $CONSUL_PORT" + +echo "$(date) Starting edgex-core-metadata..." +exec /core-metadata -cp=consul.http://"${CONSUL_HOST}":"$CONSUL_PORT" --registry --confdir=/res diff --git a/cmd/secrets-config/Attribution.txt b/cmd/secrets-config/Attribution.txt index fa6dda6f00..8cdc165eb4 100644 --- a/cmd/secrets-config/Attribution.txt +++ b/cmd/secrets-config/Attribution.txt @@ -174,3 +174,6 @@ https://github.com/go-playground/validator/blob/master/LICENSE leodido/go-urn (MIT) https://github.com/leodido/go-urn https://github.com/leodido/go-urn + +github.com/lib/pq (MIT) https://github.com/lib/pq +https://github.com/lib/pq/blob/master/LICENSE.md diff --git a/cmd/security-bootstrap-redis/Attribution.txt b/cmd/security-bootstrap-redis/Attribution.txt index 310566ab7a..b94d1b7dc5 100644 --- a/cmd/security-bootstrap-redis/Attribution.txt +++ b/cmd/security-bootstrap-redis/Attribution.txt @@ -173,3 +173,6 @@ https://github.com/go-playground/validator/blob/master/LICENSE leodido/go-urn (MIT) https://github.com/leodido/go-urn https://github.com/leodido/go-urn + +github.com/lib/pq (MIT) https://github.com/lib/pq +https://github.com/lib/pq/blob/master/LICENSE.md diff --git a/cmd/security-bootstrapper/Attribution.txt b/cmd/security-bootstrapper/Attribution.txt new file mode 100644 index 0000000000..2660c53060 --- /dev/null +++ b/cmd/security-bootstrapper/Attribution.txt @@ -0,0 +1,179 @@ +The following open source projects are referenced by Core Data Go: + +pkg/errors (BSD-2) https://github.com/pkg/errors +https://github.com/pkg/errors/blob/master/LICENSE + +gorilla/mux (BSD-3) - https://github.com/gorilla/mux +https://github.com/gorilla/mux/blob/master/LICENSE + +pebbe/zmq4 (BSD-2) https://github.com/pebbe/zmq4 +https://github.com/pebbe/zmq4/blob/master/LICENSE.txt + +go-kit/kit (MIT) github.com/go-kit/kit +https://github.com/go-kit/kit/blob/master/LICENSE + +go-logfmt/logfmt (MIT) https://github.com/go-logfmt/logfmt +https://github.com/go-logfmt/logfmt/blob/master/LICENSE + +robfig/cron (MIT) https://github.com/robfig/cron +https://github.com/robfig/cron/blob/master/LICENSE + +dgrijalva/jwt-go (MIT) https://github.com/dgrijalva/jwt-go +https://github.com/dgrijalva/jwt-go/blob/master/LICENSE + +google/uuid (BSD-3) https://github.com/google/uuid +https://github.com/google/uuid/blob/master/LICENSE + +pelletier/go-toml (MIT) https://github.com/pelletier/go-toml +https://github.com/pelletier/go-toml/blob/master/LICENSE + +influxdata/influxdb/client/v2 (MIT) https://github.com/influxdata/influxdb +https://github.com/influxdata/influxdb/blob/master/LICENSE + +influxdata/platform (MIT) https://github.com/influxdata/platform +https://github.com/influxdata/platform/blob/master/LICENSE + +eclipse/paho.mqtt.golang (Eclipse Public License 1.0) https://github.com/eclipse/paho.mqtt.golang +https://github.com/eclipse/paho.mqtt.golang/blob/master/LICENSE + +mattn/go-xmpp (BSD-3) https://github.com/mattn/go-xmpp +https://github.com/mattn/go-xmpp/blob/master/LICENSE + +BurntSushi/toml (MIT) https://github.com/BurntSushi/toml +https://github.com/BurntSushi/toml/blob/master/COPYING + +mitchellh/consulstructure (MIT) https://github.com/mitchellh/consulstructure +https://github.com/mitchellh/consulstructure/blob/master/LICENSE + +mitchellh/mapstructure (MIT) https://github.com/mitchellh/mapstructure +https://github.com/mitchellh/mapstructure/blob/master/LICENSE + +mitchellh/copystructure (MIT) https://github.com/mitchellh/copystructure +https://github.com/mitchellh/copystructure/blob/master/LICENSE + +mitchellh/reflectwalk (MIT) https://github.com/mitchellh/reflectwalk +https://github.com/mitchellh/reflectwalk/blob/master/LICENSE + +cenkalti/backoff (MIT) https://github.com/cenkalti/backoff +https://github.com/cenkalti/backoff/blob/master/LICENSE + +hashicorp/consul/api 1.1.0 (Mozilla Public License 2.0) - https://github.com/hashicorp/consul/api +https://github.com/hashicorp/consul/blob/master/LICENSE + +hashicorp/go-cleanhttp (Mozilla Public License 2.0) - https://github.com/hashicorp/go-cleanhttp +https://github.com/hashicorp/go-cleanhttp/blob/master/LICENSE + +hashicorp/go-rootcerts (Mozilla Public License 2.0) https://github.com/hashicorp/go-rootcerts +https://github.com/hashicorp/go-rootcerts/blob/master/LICENSE + +mitchellh/go-homedir (MIT) https://github.com/mitchellh/go-homedir +https://github.com/mitchellh/go-homedir/blob/master/LICENSE + +mitchellh/mapstructure (MIT) https://github.com/mitchellh/mapstructure +https://github.com/mitchellh/mapstructure/blob/master/LICENSE + +hashicorp/serf (Mozilla Public License 2.0) https://github.com/hashicorp/serf +https://github.com/hashicorp/serf/blob/master/LICENSE + +armon/go-metrics (MIT) https://github.com/armon/go-metrics +https://github.com/armon/go-metrics/blob/master/LICENSE + +hashicorp/go-immutable-radix (Mozilla Public License 2.0) https://github.com/hashicorp/go-immutable-radix +https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE + +hashicorp/golang-lru (Mozilla Public License 2.0) https://github.com/hashicorp/golang-lru +https://github.com/hashicorp/golang-lru/blob/master/LICENSE + +github.com/go-redis/redis/v7 (BSD-2) https://github.com/go-redis/redis +https://github.com/go-redis/redis/blob/master/LICENSE +https://github.com/go-redis/redis/blob/master/LICENSE + +gomodule/redigo (Apache 2.0) https://github.com/gomodule/redigo +https://github.com/gomodule/redigo/blob/master/LICENSE + +OneOfOne/xxhash (Apache 2.0) https://github.com/OneOfOne/xxhash +https://github.com/OneOfOne/xxhash/blob/master/LICENSE + +imdario/mergo (BSD-3) github.com/imdario/mergo +https://github.com/imdario/mergo/blob/master/LICENSE + +magiconair/properties (BSD-2) https://github.com/magiconair/properties +https://github.com/magiconair/properties/blob/master/LICENSE + +gopkg.in/eapache/queue.v1 (MIT) gopkg.in/eapache/queue.v1 +https://github.com/eapache/queue/blob/v1.1.0/LICENSE + +bertimus9/systemstat (MIT) https://bitbucket.org/bertimus9/systemstat +https://bitbucket.org/bertimus9/systemstat/src/master/LICENSE + +davecgh/go-spew (ISC) https://github.com/davecgh/go-spew +https://github.com/davecgh/go-spew/blob/master/LICENSE + +edgexfoundry/go-mod-bootstrap (Apache 2.0) https://github.com/edgexfoundry/go-mod-bootstrap +https://github.com/edgexfoundry/go-mod-bootstrap/blob/master/LICENSE + +edgexfoundry/go-mod-configuration (Apache 2.0) https://github.com/edgexfoundry/go-mod-configuration +https://github.com/edgexfoundry/go-mod-configuration/blob/master/LICENSE + +edgexfoundry/go-mod-core-contracts (Apache 2.0) https://github.com/edgexfoundry/go-mod-core-contracts +https://github.com/edgexfoundry/go-mod-core-contracts/blob/master/LICENSE + +edgexfoundry/go-mod-messaging (Apache 2.0) https://github.com/edgexfoundry/go-mod-messaging +https://github.com/edgexfoundry/go-mod-messaging/blob/master/LICENSE + +edgexfoundry/go-mod-registry (Apache 2.0) https://github.com/edgexfoundry/go-mod-registry +https://github.com/edgexfoundry/go-mod-registry/blob/master/LICENSE + +edgexfoundry/go-mod-secrets (Apache 2.0) https://github.com/edgexfoundry/go-mod-secrets +https://github.com/edgexfoundry/go-mod-secrets/blob/master/LICENSE + +gorilla/context (BSD-3) https://github.com/gorilla/context +https://github.com/gorilla/context/blob/master/LICENSE + +kr/logfmt (Unspecified) https://github.com/kr/logfmt +https://github.com/kr/logfmt/blob/master/Readme + +pmezard/go-difflib (Unspecified) https://github.com/pmezard/go-difflib +https://github.com/pmezard/go-difflib/blob/master/LICENSE + +stretchr/objx (MIT) https://github.com/stretchr/objx +https://github.com/stretchr/objx/blob/master/LICENSE + +stretchr/testify (MIT) https://github.com/stretchr/testify +https://github.com/stretchr/testify/blob/master/LICENSE + +fxamacker/cbor (MIT) https://github.com/fxamacker/cbor/v2 +https://github.com/fxamacker/cbor/blob/master/README.md#license + +x448/float16 (MIT) https://github.com/x448/float16 +https://github.com/x448/float16/blob/master/LICENSE + +golang.org/x/net (Unspecified) https://github.com/golang/net +https://github.com/golang/net/blob/master/LICENSE + +gopkg.in/yaml.v2 (Apache 2.0) https://github.com/go-yaml/yaml/ +https://github.com/go-yaml/yaml/blob/v2.2.2/LICENSE + +gopkg.in/yaml.v3 (MIT) https://github.com/go-yaml/yaml/ +https://github.com/go-yaml/yaml/blob/v3/LICENSE + +cloudflare/gokey (BSD-3) https://github.com/cloudflare/gokey +https://github.com/cloudflare/gokey/blob/master/LICENSE + +golang.org/x/crypto (Unspecified) https://github.com/golang/crypto +https://github.com/golang/crypto/blob/master/LICENSE + +go-playground/locales (MIT) https://github.com/go-playground/locales +https://github.com/go-playground/locales/blob/master/LICENSE + +go-playground/universal-translator (MIT) https://github.com/go-playground/universal-translator +https://github.com/go-playground/universal-translator/blob/master/LICENSE + +github.com/go-playground/validator/v10 (MIT) https://github.com/go-playground/validator +https://github.com/go-playground/validator/blob/master/LICENSE + +leodido/go-urn (MIT) https://github.com/leodido/go-urn +https://github.com/leodido/go-urn + +github.com/lib/pq (MIT) https://github.com/lib/pq +https://github.com/lib/pq/blob/master/LICENSE.md diff --git a/cmd/security-bootstrapper/Dockerfile b/cmd/security-bootstrapper/Dockerfile new file mode 100644 index 0000000000..f7050a9c30 --- /dev/null +++ b/cmd/security-bootstrapper/Dockerfile @@ -0,0 +1,82 @@ +# ---------------------------------------------------------------------------------- +# Copyright 2021 Intel Corp. +# +# 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. +# +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +ARG BUILDER_BASE=golang:1.15-alpine3.12 +FROM ${BUILDER_BASE} AS builder + +WORKDIR /edgex-go + +RUN sed -e 's/dl-cdn[.]alpinelinux.org/nl.alpinelinux.org/g' -i~ /etc/apk/repositories + +RUN apk add --update --no-cache make git + +COPY go.mod . + +RUN go mod download + +COPY . . + +RUN make cmd/security-bootstrapper/security-bootstrapper \ + && make cmd/security-bootstrap-redis/security-bootstrap-redis + +FROM alpine:3.12 + +RUN apk add --update --no-cache dumb-init openssl + +LABEL license='SPDX-License-Identifier: Apache-2.0' \ + copyright='Copyright (c) 2021 Intel Corporation' + +# Use dockerize utility for services to wait for certain ports to be available +ENV DOCKERIZE_VERSION v0.6.1 +RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ + && tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ + && rm dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz + +ENV SECURITY_INIT_DIR /edgex-init +ARG BOOTSTRAP_REDIS_DIR=${SECURITY_INIT_DIR}/bootstrap-redis + +RUN mkdir -p ${SECURITY_INIT_DIR} \ + && mkdir -p ${BOOTSTRAP_REDIS_DIR} \ + && echo "Copy dockerize executable" \ + && cp /usr/local/bin/dockerize ${SECURITY_INIT_DIR} + +WORKDIR ${SECURITY_INIT_DIR} + +# copy all entrypoint scripts into shared folder +COPY --from=builder /edgex-go/cmd/security-bootstrapper/entrypoint-scripts/ ${SECURITY_INIT_DIR}/ +RUN chmod +x ${SECURITY_INIT_DIR}/*.sh + +COPY --from=builder /edgex-go/cmd/security-bootstrapper/security-bootstrapper . +COPY --from=builder /edgex-go/cmd/security-bootstrapper/res/configuration.toml ./res/ + +# needed for bootstrapping Redis db +COPY --from=builder /edgex-go/cmd/security-bootstrap-redis/security-bootstrap-redis ${BOOTSTRAP_REDIS_DIR}/ +COPY --from=builder /edgex-go/cmd/security-bootstrap-redis/res/configuration.toml ${BOOTSTRAP_REDIS_DIR}/res/ + +# Expose the file directory as a volume since there's long-running state +VOLUME ${SECURITY_INIT_DIR} + +# The entry point script uses dumb-init as the top-level process to reap any +# zombie processes +COPY --from=builder /edgex-go/cmd/security-bootstrapper/entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/entrypoint.sh \ + && ln -s /usr/local/bin/entrypoint.sh / +ENTRYPOINT ["entrypoint.sh"] + +CMD ["install"] diff --git a/cmd/security-bootstrapper/entrypoint-scripts/consul_wait_install.sh b/cmd/security-bootstrapper/entrypoint-scripts/consul_wait_install.sh new file mode 100755 index 0000000000..bfbb0e5c39 --- /dev/null +++ b/cmd/security-bootstrapper/entrypoint-scripts/consul_wait_install.sh @@ -0,0 +1,66 @@ +#!/usr/bin/dumb-init /bin/sh +# ---------------------------------------------------------------------------------- +# Copyright (c) 2021 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +# This is customized entrypoint script for Consul. +# In particular, it waits for Vault to be ready to roll + +set -e + +# function to check on Vault for readiness +vault_ready() +{ + vault_host=$1 + vault_port=$2 + resp_code=$(curl --write-out '%{http_code}' --silent --output /dev/null "${vault_host}":"$vault_port"/v1/sys/health) + if [ "$resp_code" -eq 200 ] ; then + echo 1 + else + echo 0 + fi +} + +# env settings +. /edgex-init/.env-consul + +echo "Script for waiting security bootstrapping installation" + +echo "$(date) Consul waits on Vault to be initialized" +vault_inited=$(vault_ready "${EDGEX_VAULT_HOST}" "$EDGEX_VAULT_PORT") +until [ "$vault_inited" -eq 1 ]; do + echo "$(date) waiting for Vault to be initialized"; + sleep 1; + vault_inited=$(vault_ready "${EDGEX_VAULT_HOST}" "$EDGEX_VAULT_PORT") +done; + +echo "$(date) Starting edgex-consul..." +exec /usr/local/bin/edgex-consul-entrypoint.sh agent -ui -bootstrap -server -client 0.0.0.0 & + +# wait for the consul port +echo "$(date) Executing dockerize on Consul with waiting on its own port tcp://${CONSUL_HOST}:$CONSUL_PORT" +/edgex-init/dockerize -wait tcp://"${CONSUL_HOST}":"$CONSUL_PORT" -timeout 30s + +# signal the consul is ready +# in a forever loop so that it keeps listening all the times +while true; do + /edgex-init/security-bootstrapper --confdir=/edgex-init/res listenTcp \ + --port="$CONSUL_READY_PORT" --host="${CONSUL_HOST}" + if [ $? -ne 0 ]; then + echo "$(date) failed to gating the consul ready port" + fi +done diff --git a/cmd/security-bootstrapper/entrypoint-scripts/kong_wait_install.sh b/cmd/security-bootstrapper/entrypoint-scripts/kong_wait_install.sh new file mode 100755 index 0000000000..ac654e6f2f --- /dev/null +++ b/cmd/security-bootstrapper/entrypoint-scripts/kong_wait_install.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# ---------------------------------------------------------------------------------- +# Copyright (c) 2021 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +# This is customized entrypoint script for Kong. +# In particular, it waits for the ReadyToRunPort ready to roll + +set -e + +# env settings +. /edgex-init/.env-kong + +echo "Script for waiting security bootstrapping installation" + +# gating on the ready-to-run port +echo "$(date) Executing dockerize with waiting on tcp://${BOOTSTRAPPER_HOST}:$WAIT_TCP_PORT" +/edgex-init/dockerize -wait tcp://"${BOOTSTRAPPER_HOST}":"$WAIT_TCP_PORT" -timeout 60s + +echo "$(date) Kong waits on Postgres to be initialized" +/edgex-init/dockerize -wait tcp://"${POSTGRES_HOST}":"$POSTGRES_PORT" -timeout 60s + +echo "$(date) Executing dockerize with waiting on file:${POSTGRES_PASSWORD_FILE}" +/edgex-init/dockerize -wait file://"${POSTGRES_PASSWORD_FILE}" -timeout 60s + + +# double check and make sure the postgres is setup with that password and ready +passwd=$(cat "${POSTGRES_PASSWORD_FILE}") +pg_inited=0 +until [ $pg_inited -eq 1 ]; do + status=$(/edgex-init/security-bootstrapper --confdir=/edgex-init/res pingPgDb \ + --username=kong --dbname=kong --password="${passwd}" | tail -n 1) + if [ ${#status} -gt 0 ] && [[ "${status}" != *ERROR* ]]; then + if [ "${status}" = "ready" ]; then + pg_inited=1 + passwd="" + fi + fi + if [ $pg_inited -ne 1 ]; then + echo "$(date) waiting for ${POSTGRES_HOST} to be initialized" + sleep 1 + fi +done; + +echo "$(date) Check point: postgres db is ready for kong" + +# in kong's docker, we use KONG_PG_PASSWORD_FILE instead of KONG_PG_PASSWORD for better security +KONG_PG_PASSWORD_FILE=${POSTGRES_PASSWORD_FILE} +export KONG_PG_PASSWORD_FILE + +/docker-entrypoint.sh kong migrations bootstrap +/docker-entrypoint.sh kong migrations list +code=$? +if [ $code -eq 0 ]; then + echo "$(date) kong migrations bootstrap ok, doing migrations up and finish..." + /docker-entrypoint.sh kong migrations up && /docker-entrypoint.sh kong migrations finish +else + echo "$(date) failed to kong migrations, returned code = " $code +fi; + +# remove env KONG_PG_PASSWORD: only use KONG_PG_PASSWORD_FILE +unset KONG_PG_PASSWORD + +echo "$(date) Starting kong ..." +exec /docker-entrypoint.sh kong docker-start diff --git a/cmd/security-bootstrapper/entrypoint-scripts/others_wait_install.sh b/cmd/security-bootstrapper/entrypoint-scripts/others_wait_install.sh new file mode 100755 index 0000000000..2c808c4e09 --- /dev/null +++ b/cmd/security-bootstrapper/entrypoint-scripts/others_wait_install.sh @@ -0,0 +1,41 @@ +#!/usr/bin/dumb-init /bin/sh +# ---------------------------------------------------------------------------------- +# Copyright (c) 2021 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +# This is customized entrypoint script for other Edgex services. +# In particular, it waits for the ReadyToRunPort raised to be ready to roll + +set -e + +# env settings +. /edgex-init/.env-others + +echo "Script for waiting security bootstrapping installation" + +# gating on the ready-to-run port +echo "$(date) Executing dockerize with $@ waiting on tcp://${BOOTSTRAPPER_HOST}:$WAIT_TCP_PORT" +/edgex-init/dockerize -wait tcp://"${BOOTSTRAPPER_HOST}":"$WAIT_TCP_PORT" -timeout 60s + +echo "$(date) Starting $@ ..." +# If the user is trying to run the service with some arguments, then execute them directly +# this can be useful if the user wants to run the service with non-default parameters +if [ "${1:0:1}" = '-' ]; then + exec "$@" +else + exec /"$@" -cp=consul.http://"${CONSUL_HOST}":"$CONSUL_PORT" --registry --confdir=/res +fi diff --git a/cmd/security-bootstrapper/entrypoint-scripts/postgres_wait_install.sh b/cmd/security-bootstrapper/entrypoint-scripts/postgres_wait_install.sh new file mode 100755 index 0000000000..c147bedc63 --- /dev/null +++ b/cmd/security-bootstrapper/entrypoint-scripts/postgres_wait_install.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# ---------------------------------------------------------------------------------- +# Copyright (c) 2021 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +# This is customized entrypoint script for Postgres. +# In particular, it waits for Vault to be ready and dynamically generated password seeded into db + +set -e + +# env settings +. /edgex-init/.env-postgres + +echo "Script for waiting security bootstrapping installation" + +echo "$(date) Executing dockerize on Postgres with waiting on tcp://${BOOTSTRAPPER_HOST}:$WAIT_TCP_PORT" +/edgex-init/dockerize -wait tcp://"${BOOTSTRAPPER_HOST}":"$WAIT_TCP_PORT" -timeout 30s + +echo "$(date) Postgres waits on Vault to be initialized" + +vault_inited=0 +until [ $vault_inited -eq 1 ]; do + status=$(/edgex-init/security-bootstrapper --confdir=/edgex-init/res getHttpStatus \ + --url=http://"${EDGEX_VAULT_HOST}":"$EDGEX_VAULT_PORT"/v1/sys/health | tail -n 1) + if [ ${#status} -gt 0 ] && [[ "${status}" != *ERROR* ]]; then + echo "$(date) ${EDGEX_VAULT_HOST} status code = ${status}" + if [ "$status" -eq 200 ]; then + vault_inited=1 + fi + fi + if [ $vault_inited -ne 1 ]; then + echo "$(date) waiting for ${EDGEX_VAULT_HOST} to be initialized" + sleep 1 + fi +done; + +echo "$(date) ${EDGEX_VAULT_HOST} is ready" + +# if password already in then re-use +if [ -n "${POSTGRES_PASSWORD_FILE}" ] && [ -f "${POSTGRES_PASSWORD_FILE}" ]; then + echo "$(date) previous file already exists, skipping creation" +else + # create password file for postgres to be used in the compose file + mkdir -p "$(dirname "${POSTGRES_PASSWORD_FILE}")" + out=$(/edgex-init/security-bootstrapper --confdir=/edgex-init/res genPassword | tail -n 1) + if [ ${#out} -gt 0 ] && [[ "${out}" != *ERROR* ]]; then + echo "${out}" > "${POSTGRES_PASSWORD_FILE}" + fi +fi + +chmod 444 "${POSTGRES_PASSWORD_FILE}" +export POSTGRES_PASSWORD_FILE + +echo "$(date) Starting kong-db..." +exec /usr/local/bin/docker-entrypoint.sh postgres & + +# check that the postgres is initialized +passwd=$(cat "${POSTGRES_PASSWORD_FILE}") +pg_inited=0 +until [ $pg_inited -eq 1 ]; do + status=$(/edgex-init/security-bootstrapper --confdir=/edgex-init/res pingPgDb \ + --username=kong --dbname=kong --password="${passwd}" | tail -n 1) + if [ ${#status} -gt 0 ] && [[ "${status}" != *ERROR* ]]; then + if [ "${status}" = "ready" ]; then + pg_inited=1 + passwd="" + fi + fi + if [ $pg_inited -ne 1 ]; then + echo "$(date) waiting for ${POSTGRES_HOST} to be initialized" + sleep 1 + fi +done; + +echo "$(date) ${POSTGRES_HOST} is initialized" + +# signal the postgres is ready +# in a forever loop so that it keeps listening all the times +while true; do + /edgex-init/security-bootstrapper --confdir=/edgex-init/res listenTcp \ + --port="$POSTGRES_READY_PORT" --host="${POSTGRES_HOST}" + if [ $? -ne 0 ]; then + echo "$(date) failed to gating the postgres ready port" + fi +done diff --git a/cmd/security-bootstrapper/entrypoint-scripts/proxy_setup_wait_install.sh b/cmd/security-bootstrapper/entrypoint-scripts/proxy_setup_wait_install.sh new file mode 100755 index 0000000000..66362f1abc --- /dev/null +++ b/cmd/security-bootstrapper/entrypoint-scripts/proxy_setup_wait_install.sh @@ -0,0 +1,54 @@ +#!/bin/sh +# ---------------------------------------------------------------------------------- +# Copyright (c) 2021 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +# This is customized entrypoint script for Postgres. +# In particular, it waits for Vault to be ready and dynamically generated password seeded into db + +set -e + +# env settings +. /edgex-init/.env-proxy-setup + +echo "Script for waiting security bootstrapping installation" + +# gating on the ready-to-run port +echo "$(date) Executing dockerize on ${PROXY_SETUP_HOST} with waiting on tcp://${BOOTSTRAPPER_HOST}:$WAIT_TCP_PORT" +/edgex-init/dockerize -wait tcp://"${BOOTSTRAPPER_HOST}":"$WAIT_TCP_PORT" -timeout 30s + +echo "$(date) ${PROXY_SETUP_HOST} waits on Kong to be initialized" + +kong_inited=0 +until [ $kong_inited -eq 1 ]; do + status=$(/edgex-init/security-bootstrapper --confdir=/edgex-init/res getHttpStatus \ + --url=http://"${KONG_HOST}":"$KONG_STATUS_PORT"/status | tail -n 1) + if [ ${#status} -gt 0 ] && [[ "${status}" != *ERROR* ]]; then + echo "$(date) ${KONG_HOST}:$KONG_STATUS_PORT status code = ${status}" + if [ "$status" -eq 200 ]; then + kong_inited=1 + fi + fi + if [ $kong_inited -ne 1 ]; then + echo "$(date) waiting for ${KONG_HOST} to be initialized" + sleep 1 + fi +done + +echo "$(date) ${KONG_HOST} is ready" + +exec /edgex/security-proxy-setup --init=true diff --git a/cmd/security-bootstrapper/entrypoint-scripts/redis_wait_install.sh b/cmd/security-bootstrapper/entrypoint-scripts/redis_wait_install.sh new file mode 100755 index 0000000000..96e83bbd25 --- /dev/null +++ b/cmd/security-bootstrapper/entrypoint-scripts/redis_wait_install.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# ---------------------------------------------------------------------------------- +# Copyright (c) 2021 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +# This is customized entrypoint script for Redis. +# In particular, it waits for the TokensReady Port being ready to roll + +set -e + +# env settings +. /edgex-init/.env-redis + +echo "Script for waiting security bootstrapping installation" + +# gating on the TokensReadyPort +echo "$(date) Executing dockerize on Redis with waiting on TokensReadyPort tcp://${VAULTWORKER_HOST}:$WAIT_TCP_PORT" +/edgex-init/dockerize -wait tcp://"${VAULTWORKER_HOST}":"$WAIT_TCP_PORT" -timeout 60s + +# the bootstrap-redis needs the connection from Redis db to set it up. +# Hence, here bootstrap-redis runs in background and then starts the Redis db. +echo "$(date) ${VAULTWORKER_HOST} tokens ready, bootstrapping redis..." +/edgex-init/bootstrap-redis/security-bootstrap-redis --confdir=/edgex-init/bootstrap-redis/res & + +# give some time for bootstrap-redis to start up +sleep 1 + +echo "$(date) Starting edgex-redis..." +exec /usr/local/bin/docker-entrypoint.sh redis-server & + +# signal the redis is ready +# in a forever loop so that it keeps listening all the times +while true; do + /edgex-init/security-bootstrapper --confdir=/edgex-init/res listenTcp \ + --port="$REDIS_READY_PORT" --host="${REDIS_HOST}" + if [ $? -ne 0 ]; then + echo "$(date) failed to gating the redis ready port" + fi +done diff --git a/cmd/security-bootstrapper/entrypoint-scripts/vault_wait_install.sh b/cmd/security-bootstrapper/entrypoint-scripts/vault_wait_install.sh new file mode 100755 index 0000000000..c4cbcdf515 --- /dev/null +++ b/cmd/security-bootstrapper/entrypoint-scripts/vault_wait_install.sh @@ -0,0 +1,54 @@ +#!/usr/bin/dumb-init /bin/sh +# ---------------------------------------------------------------------------------- +# Copyright (c) 2021 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +# This is customized entrypoint script for Vault. +# In particular, it waits for the BootstrapPort ready to roll + +set -e + +# env settings +. /edgex-init/.env-vault + +echo "Script for waiting security bootstrapping installation" + +DEFAULT_VAULT_LOCAL_CONFIG=' +listener "tcp" { + address = "edgex-vault:8200" + tls_disable = "1" + cluster_address = "edgex-vault:8201" + } + backend "file" { + path = "/vault/file" + } + default_lease_ttl = "168h" + max_lease_ttl = "720h" +' + +VAULT_LOCAL_CONFIG=${VAULT_LOCAL_CONFIG:-$DEFAULT_VAULT_LOCAL_CONFIG} + +export VAULT_LOCAL_CONFIG + +echo "$(date) VAULT_LOCAL_CONFIG: ${VAULT_LOCAL_CONFIG}" + +if [ "$1" = 'server' ]; then + echo "$(date) Executing dockerize on vault $* with waiting on tcp://${BOOTSTRAPPER_HOST}:$WAIT_TCP_PORT" + /edgex-init/dockerize -wait tcp://"${BOOTSTRAPPER_HOST}":"$WAIT_TCP_PORT" -timeout 60s + echo "$(date) Starting edgex-vault..." + exec /usr/local/bin/docker-entrypoint.sh server -log-level=info +fi diff --git a/cmd/security-bootstrapper/entrypoint.sh b/cmd/security-bootstrapper/entrypoint.sh new file mode 100755 index 0000000000..19729adb95 --- /dev/null +++ b/cmd/security-bootstrapper/entrypoint.sh @@ -0,0 +1,209 @@ +#!/usr/bin/dumb-init /bin/sh +# ---------------------------------------------------------------------------------- +# Copyright (c) 2021 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ---------------------------------------------------------------------------------- + +set -e + +# function to trim off leading and trailing spaces +trim_spaces() +{ + trimmed=$(echo -e "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + echo "${trimmed}" +} + +# Passing the arguments to the executable +if [ ! "${1:0:1}" = '/bin/' ]; then + set -- security-bootstrapper "$@" +fi + +INIT_DIR=/edgex-init/ + +# get the configuration settings from the configuration toml file +TOML_FILE="${INIT_DIR}/res/configuration.toml" +[[ -e ${TOML_FILE} ]] || { echo "${TOML_FILE} does not exist." >&2;exit 1;} + +IFS="=" +BOOTSTRAPPER_HOST_KEY="BootstrapperHost" +BOOTSTRAP_PORT_KEY="BootstrapPort" +VAULT_WORKER_HOST_KEY="VaultWorkerHost" +TOKENS_READY_PORT_KEY="TokensReadyPort" +READY_TO_RUN_PORT_KEY="ReadyToRunPort" +CONSUL_HOST_KEY="ConsulHost" +CONSUL_PORT_KEY="ConsulPort" +CONSUL_READY_PORT_KEY="ConsulReadyPort" +POSTGRES_HOST_KEY="PostgresHost" +POSTGRES_PORT_KEY="PostgresPort" +POSTGRES_READY_PORT_KEY="PostgresReadyPort" +REDIS_HOST_KEY="RedisHost" +REDIS_PORT_KEY="RedisPort" +REDIS_READY_PORT_KEY="RedisReadyPort" +PROXY_SETUP_HOST_KEY="ProxySetupHost" +bootstrapper_host="" +bootstrap_port_number=0 +vaultworker_host="" +tokens_ready_port_number=0 +ready_to_run_port_number=0 +consul_host="" +consul_port_number=0 +consul_ready_port_number=0 +postgres_host="" +postgres_port_number=0 +postgres_ready_port_number=0 +redis_host="" +redis_port_number=0 +redis_ready_port_number=0 +proxy_setup_host="" + +while read -r key value +do + trimmed_key=$(trim_spaces "${key}") + trimmed_value=$(trim_spaces "${value}") + + if [ "${trimmed_key}" = "${BOOTSTRAPPER_HOST_KEY}" ]; then + bootstrapper_host=${trimmed_value} + elif [ "${trimmed_key}" = "${BOOTSTRAP_PORT_KEY}" ]; then + bootstrap_port_number=${trimmed_value} + elif [ "${trimmed_key}" = "${VAULT_WORKER_HOST_KEY}" ]; then + vaultworker_host=${trimmed_value} + elif [ "${trimmed_key}" = "${TOKENS_READY_PORT_KEY}" ]; then + tokens_ready_port_number=${trimmed_value} + elif [ "${trimmed_key}" = "${READY_TO_RUN_PORT_KEY}" ]; then + ready_to_run_port_number=${trimmed_value} + elif [ "${trimmed_key}" = "${CONSUL_HOST_KEY}" ]; then + consul_host=${trimmed_value} + elif [ "${trimmed_key}" = "${CONSUL_PORT_KEY}" ]; then + consul_port_number=${trimmed_value} + elif [ "${trimmed_key}" = "${CONSUL_READY_PORT_KEY}" ]; then + consul_ready_port_number=${trimmed_value} + elif [ "${trimmed_key}" = "${POSTGRES_HOST_KEY}" ]; then + postgres_host=${trimmed_value} + elif [ "${trimmed_key}" = "${POSTGRES_PORT_KEY}" ]; then + postgres_port_number=${trimmed_value} + elif [ "${trimmed_key}" = "${POSTGRES_READY_PORT_KEY}" ]; then + postgres_ready_port_number=${trimmed_value} + elif [ "${trimmed_key}" = "${REDIS_HOST_KEY}" ]; then + redis_host=${trimmed_value} + elif [ "${trimmed_key}" = "${REDIS_PORT_KEY}" ]; then + redis_port_number=${trimmed_value} + elif [ "${trimmed_key}" = "${REDIS_READY_PORT_KEY}" ]; then + redis_ready_port_number=${trimmed_value} + elif [ "${trimmed_key}" = "${PROXY_SETUP_HOST_KEY}" ]; then + proxy_setup_host=${trimmed_value} + fi +done < ${TOML_FILE} + +echo bootstrapper_host: "${bootstrapper_host}" vaultworker_host: "${vaultworker_host}" postgres_host: "${postgres_host}" +echo consul_host: "${consul_host}" redis_host: "${redis_host}" proxy_setup_host: "${proxy_setup_host}" +echo bootstrap_port_number: "$bootstrap_port_number" tokens_ready_port_number: "$tokens_ready_port_number" +echo ready_to_run_port_number: "$ready_to_run_port_number" postgres_port_number: "$postgres_port_number" +echo consul_ready_port_number: "$consul_ready_port_number" redis_ready_port_number: "$redis_ready_port_number" + +EDGEX_VAULT_HOST="edgex-vault" +EDGEX_VAULT_PORT=8200 +KONG_HOST="kong" +KONG_STATUS_PORT=8001 +VAULT_ENV_FILE=.env-vault +VAULT_WORKER_ENV_FILE=.env-vault-worker +CONSUL_ENV_FILE=.env-consul +POSTGRES_ENV_FILE=.env-postgres +REDIS_ENV_FILE=.env-redis +KONG_ENV_FILE=.env-kong +PROXY_SETUP_ENV_FILE=.env-proxy-setup +OTHERS_ENV_FILE=.env-others +POSTGRES_PASSWORD_FILE="/tmp/postgres-config/.pgpassword" + +if [ "$1" = 'security-bootstrapper' ]; then + echo "Preparing ${EDGEX_VAULT_HOST} environment settings..." + cat >${INIT_DIR}${VAULT_ENV_FILE} <${INIT_DIR}${VAULT_WORKER_ENV_FILE} <${INIT_DIR}${CONSUL_ENV_FILE} <${INIT_DIR}${POSTGRES_ENV_FILE} <${INIT_DIR}${REDIS_ENV_FILE} <${INIT_DIR}${KONG_ENV_FILE} <${INIT_DIR}${PROXY_SETUP_ENV_FILE} <${INIT_DIR}${OTHERS_ENV_FILE} < [arg...]\n"+ + "Options:\n"+ + " -h, --help Show this message\n"+ + " --confdir Specify local configuration directory\n"+ + "\n"+ + "Commands:\n"+ + " help Show available commands (this text)\n"+ + " install Do security bootstrapping\n"+ + " listenTcp Start up a TCP listener\n"+ + " pingPgDb Test Postgres database readiness\n"+ + " getHttpStatus Do an HTTP GET call to get the status code\n"+ + " genPassword Generate a random password\n", + os.Args[0]) +} diff --git a/internal/security/bootstrapper/command/genpassword/command.go b/internal/security/bootstrapper/command/genpassword/command.go new file mode 100644 index 0000000000..53e4ada00a --- /dev/null +++ b/internal/security/bootstrapper/command/genpassword/command.go @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 genpassword + +import ( + "context" + "crypto/rand" + "encoding/base64" + "flag" + "fmt" + "os" + "strings" + "sync" + + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/config" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/interfaces" + + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" +) + +const ( + CommandName string = "genPassword" + + randomBytesLength = 33 // 264 bits of entropy +) + +type cmd struct { + loggingClient logger.LoggingClient + config *config.ConfigurationStruct +} + +func NewCommand( + _ context.Context, + _ *sync.WaitGroup, + lc logger.LoggingClient, + conf *config.ConfigurationStruct, + args []string) (interfaces.Command, error) { + + cmd := cmd{ + loggingClient: lc, + config: conf, + } + var dummy string + + flagSet := flag.NewFlagSet(CommandName, flag.ContinueOnError) + flagSet.StringVar(&dummy, "confdir", "", "") // handled by bootstrap; duplicated here to prevent arg parsing errors + + err := flagSet.Parse(args) + if err != nil { + return nil, fmt.Errorf("Unable to parse command: %s: %w", strings.Join(args, " "), err) + } + + return &cmd, err +} + +func (c *cmd) Execute() (int, error) { + c.loggingClient.Info(fmt.Sprintf("Security bootstrapper running %s", CommandName)) + + randomBytes := make([]byte, randomBytesLength) + _, err := rand.Read(randomBytes) // all of salt guaranteed to be filled if err==nil + if err != nil { + return interfaces.StatusCodeExitWithError, err + } + + randPass := base64.StdEncoding.EncodeToString(randomBytes) + // output the randPass to stdout + fmt.Fprintln(os.Stdout, randPass) + + return interfaces.StatusCodeExitNormal, nil +} + +func (c *cmd) GetCommandName() string { + return CommandName +} diff --git a/internal/security/bootstrapper/command/gethttpstatus/command.go b/internal/security/bootstrapper/command/gethttpstatus/command.go new file mode 100644 index 0000000000..7e883468eb --- /dev/null +++ b/internal/security/bootstrapper/command/gethttpstatus/command.go @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 gethttpstatus + +import ( + "context" + "flag" + "fmt" + "net/http" + "net/url" + "os" + "strings" + "sync" + + "github.com/edgexfoundry/edgex-go/internal" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/config" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/interfaces" + "github.com/edgexfoundry/edgex-go/internal/security/secretstoreclient" + + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" +) + +const ( + CommandName string = "getHttpStatus" +) + +type cmd struct { + loggingClient logger.LoggingClient + client internal.HttpCaller + configuration *config.ConfigurationStruct + + // options + httpURL string +} + +func NewCommand( + _ context.Context, + _ *sync.WaitGroup, + lc logger.LoggingClient, + configuration *config.ConfigurationStruct, + args []string) (interfaces.Command, error) { + + cmd := cmd{ + loggingClient: lc, + client: secretstoreclient.NewRequestor(lc).Insecure(), + configuration: configuration, + } + var dummy string + + flagSet := flag.NewFlagSet(CommandName, flag.ContinueOnError) + flagSet.StringVar(&dummy, "confdir", "", "") // handled by bootstrap; duplicated here to prevent arg parsing errors + flagSet.StringVar(&cmd.httpURL, "url", "", "get the status code returning from the input http URL address") + + err := flagSet.Parse(args) + if err != nil { + return nil, fmt.Errorf("Unable to parse command: %s: %w", strings.Join(args, " "), err) + } + + if len(cmd.httpURL) == 0 { + return nil, fmt.Errorf("%s %s: argument --url is required", os.Args[0], CommandName) + } + + return &cmd, err +} + +func (c *cmd) GetCommandName() string { + return CommandName +} + +func (c *cmd) Execute() (int, error) { + c.loggingClient.Info(fmt.Sprintf("Security bootstrapper running %s", CommandName)) + + _, err := url.Parse(c.httpURL) + if err != nil { + return interfaces.StatusCodeExitWithError, err + } + + c.loggingClient.Info(fmt.Sprintf("http calls on the endpoint of %s", c.httpURL)) + req, err := http.NewRequest(http.MethodGet, c.httpURL, http.NoBody) + if err != nil { + return interfaces.StatusCodeExitWithError, fmt.Errorf("Failed to prepare request for http URL: %w", err) + } + resp, err := c.client.Do(req) + if err != nil { + return interfaces.StatusCodeExitWithError, fmt.Errorf("Failed to send request for http URL: %w", err) + } + + defer func() { + _ = resp.Body.Close() + }() + + // don't care about the response body, only the status code + fmt.Fprintln(os.Stdout, resp.StatusCode) + + return interfaces.StatusCodeExitNormal, nil +} diff --git a/internal/security/bootstrapper/command/help/command.go b/internal/security/bootstrapper/command/help/command.go new file mode 100644 index 0000000000..d92a05db48 --- /dev/null +++ b/internal/security/bootstrapper/command/help/command.go @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 help + +import ( + "flag" + "fmt" + "strings" + + bootstrapper "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/command" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/config" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/interfaces" + + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" +) + +const ( + CommandName = "help" +) + +type cmd struct { + loggingClient logger.LoggingClient + configuration *config.ConfigurationStruct + flagSet *flag.FlagSet +} + +func NewCommand( + lc logger.LoggingClient, + configuration *config.ConfigurationStruct, + args []string) (interfaces.Command, error) { + + flagSet := flag.NewFlagSet(CommandName, flag.ContinueOnError) + err := flagSet.Parse(args) + if err != nil { + return nil, fmt.Errorf("Unable to parse command: %s: %w", strings.Join(args, " "), err) + } + + return &cmd{ + loggingClient: lc, + configuration: configuration, + flagSet: flagSet, + }, nil +} + +func (c *cmd) Execute() (statusCode int, err error) { + bootstrapper.HelpCallback() + return interfaces.StatusCodeExitNormal, nil +} + +func (c *cmd) GetCommandName() string { + return CommandName +} diff --git a/internal/security/bootstrapper/command/help/command_test.go b/internal/security/bootstrapper/command/help/command_test.go new file mode 100644 index 0000000000..50454b72fa --- /dev/null +++ b/internal/security/bootstrapper/command/help/command_test.go @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 help + +import ( + "testing" + + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/config" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/interfaces" + + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + + "github.com/stretchr/testify/require" +) + +// TestHelp tests functionality of help command +func TestHelp(t *testing.T) { + // Arrange + lc := logger.MockLogger{} + config := &config.ConfigurationStruct{} + + // Act + command, err := NewCommand(lc, config, []string{}) + require.NoError(t, err) + + code, err := command.Execute() + + // Assert + require.NoError(t, err) + require.Equal(t, interfaces.StatusCodeExitNormal, code) +} + +// TestHelpBadArg tests unknown arg handler +func TestHelpBadArg(t *testing.T) { + // Arrange + lc := logger.MockLogger{} + config := &config.ConfigurationStruct{} + + // Act + command, err := NewCommand(lc, config, []string{"-badarg"}) + + // Assert + require.Error(t, err) + require.Nil(t, command) +} diff --git a/internal/security/bootstrapper/command/install/command.go b/internal/security/bootstrapper/command/install/command.go new file mode 100644 index 0000000000..ea9fced9b1 --- /dev/null +++ b/internal/security/bootstrapper/command/install/command.go @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 install + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "sync" + "time" + + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/config" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/interfaces" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/tcp" + + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" +) + +const ( + CommandName string = "install" +) + +type cmd struct { + cntx context.Context + waitGroup *sync.WaitGroup + loggingClient logger.LoggingClient + config *config.ConfigurationStruct +} + +func NewCommand( + ctx context.Context, + wg *sync.WaitGroup, + lc logger.LoggingClient, + conf *config.ConfigurationStruct, + args []string) (interfaces.Command, error) { + + cmd := cmd{ + loggingClient: lc, + config: conf, + cntx: ctx, + waitGroup: wg, + } + var dummy string + + flagSet := flag.NewFlagSet(CommandName, flag.ContinueOnError) + flagSet.StringVar(&dummy, "confdir", "", "") // handled by bootstrap; duplicated here to prevent arg parsing errors + + err := flagSet.Parse(args) + if err != nil { + return nil, fmt.Errorf("Unable to parse command: %s: %w", strings.Join(args, " "), err) + } + + return &cmd, err +} + +func (c *cmd) Execute() (statusCode int, err error) { + c.loggingClient.Info(fmt.Sprintf("Security bootstrapper running %s", CommandName)) + + bootstrapServer := tcp.NewTcpServer() + c.loggingClient.Debug(fmt.Sprintf("init phase: attempts to start up the listener on bootstrap port: %d", + c.config.StageGate.BootstrapPort)) + + gatingSemaphorePort(bootstrapServer, c.config.StageGate.BootstrapPort, c.loggingClient, + "Raised bootstrap semaphore for secure bootstrapping") + + // wait on for others to be done: each of tcp dialers is a blocking call + c.loggingClient.Debug("Waiting on dependent semaphores required to raise the ready-to-run semaphore ...") + if err := tcp.DialTcp(c.config.StageGate.ConsulHost, c.config.StageGate.ConsulReadyPort, c.loggingClient); err != nil { + c.loggingClient.Error(err.Error()) + } + c.loggingClient.Info("Consul is ready") + + if err := tcp.DialTcp(c.config.StageGate.PostgresHost, c.config.StageGate.PostgresReadyPort, c.loggingClient); err != nil { + c.loggingClient.Error(err.Error()) + } + c.loggingClient.Info("Postgres is ready") + + if err := tcp.DialTcp(c.config.StageGate.RedisHost, c.config.StageGate.RedisReadyPort, c.loggingClient); err != nil { + c.loggingClient.Error(err.Error()) + } + c.loggingClient.Info("Redis is ready") + + // Reached ready-to-run phase + c.loggingClient.Debug(fmt.Sprintf("ready-to-run phase: attempts to start up the listener on ready-to-run port: %d", + c.config.StageGate.ReadyToRunPort)) + + readToRunServer := tcp.NewTcpServer() + + gatingSemaphorePort(readToRunServer, c.config.StageGate.ReadyToRunPort, c.loggingClient, + "Raised ready-to-run semaphore for secure bootstrapping") + + // keep running until ctx done + c.waitGroup.Add(1) + go func() { + defer c.waitGroup.Done() + + <-c.cntx.Done() + c.loggingClient.Info("security bootstrapper install finished") + }() + + return +} + +func (c *cmd) GetCommandName() string { + return CommandName +} + +func gatingSemaphorePort(tcpServer *tcp.TcpServer, portNum int, lc logger.LoggingClient, raisedMsg string) { + // in a separated goroutine so that it won't block the main thread + go func() { + if err := tcpServer.StartListener(portNum, lc, ""); err != nil { + // listener is blocking forever until some internal critical error happens + lc.Error(err.Error()) + os.Exit(1) + } + }() + + for { + // looping until listener accepts, in which case the port semaphore is raised + accepted := tcpServer.GetAcceptStatus() + if accepted { + lc.Info(raisedMsg) + break + } + + // give a little breathing room + time.Sleep(100 * time.Millisecond) + } +} diff --git a/internal/security/bootstrapper/command/listen/command.go b/internal/security/bootstrapper/command/listen/command.go new file mode 100644 index 0000000000..9c32928985 --- /dev/null +++ b/internal/security/bootstrapper/command/listen/command.go @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 listen + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "sync" + "time" + + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/config" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/interfaces" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/tcp" + + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" +) + +const ( + CommandName string = "listenTcp" +) + +type cmd struct { + loggingClient logger.LoggingClient + config *config.ConfigurationStruct + + // options: + tcpHost string + tcpPort int +} + +func NewCommand( + _ context.Context, + _ *sync.WaitGroup, + lc logger.LoggingClient, + conf *config.ConfigurationStruct, + args []string) (interfaces.Command, error) { + + cmd := cmd{ + loggingClient: lc, + config: conf, + } + var dummy string + + flagSet := flag.NewFlagSet(CommandName, flag.ContinueOnError) + flagSet.StringVar(&dummy, "confdir", "", "") // handled by bootstrap; duplicated here to prevent arg parsing errors + flagSet.StringVar(&cmd.tcpHost, "host", "", "the hostname of TCP server to listen ") + + flagSet.IntVar(&cmd.tcpPort, "port", 0, "the port number of TCP server to listen ") + + err := flagSet.Parse(args) + if err != nil { + return nil, fmt.Errorf("Unable to parse command: %s: %w", strings.Join(args, " "), err) + } + + if cmd.tcpPort == 0 { + return nil, fmt.Errorf("%s %s: argument --port is required", os.Args[0], CommandName) + } + + return &cmd, err +} + +func (c *cmd) Execute() (int, error) { + c.loggingClient.Info(fmt.Sprintf("Security bootstrapper running %s", CommandName)) + + tcpServer := tcp.NewTcpServer() + err := make(chan error, 1) + go func() { + err <- tcpServer.StartListener(c.tcpPort, c.loggingClient, c.tcpHost) + }() + + quit := false + for !quit { + select { + case tcpErr := <-err: + return interfaces.StatusCodeExitWithError, tcpErr + default: + accepted := tcpServer.GetAcceptStatus() + if accepted { + c.loggingClient.Info("Accepted TCP connection, quits listener") + quit = true + } + + // give a little breathing room + time.Sleep(100 * time.Millisecond) + } + } + + return interfaces.StatusCodeExitNormal, nil +} + +func (c *cmd) GetCommandName() string { + return CommandName +} diff --git a/internal/security/bootstrapper/command/ping/command.go b/internal/security/bootstrapper/command/ping/command.go new file mode 100644 index 0000000000..128bc712ec --- /dev/null +++ b/internal/security/bootstrapper/command/ping/command.go @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 ping + +import ( + "context" + "database/sql" + "flag" + "fmt" + "os" + "strconv" + "strings" + "sync" + + "github.com/edgexfoundry/edgex-go/internal" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/config" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/interfaces" + "github.com/edgexfoundry/edgex-go/internal/security/secretstoreclient" + + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + + _ "github.com/lib/pq" +) + +const ( + CommandName string = "pingPgDb" +) + +type cmd struct { + loggingClient logger.LoggingClient + client internal.HttpCaller + configuration *config.ConfigurationStruct + + // options + host string + port int + username string + dbname string + pwd string +} + +func NewCommand( + _ context.Context, + _ *sync.WaitGroup, + lc logger.LoggingClient, + configuration *config.ConfigurationStruct, + args []string) (interfaces.Command, error) { + + cmd := cmd{ + loggingClient: lc, + client: secretstoreclient.NewRequestor(lc).Insecure(), + configuration: configuration, + } + + var dummy string + + flagSet := flag.NewFlagSet(CommandName, flag.ContinueOnError) + flagSet.StringVar(&dummy, "confdir", "", "") // handled by bootstrap; duplicated here to prevent arg parsing errors + + flagSet.StringVar(&cmd.host, "host", cmd.configuration.StageGate.PostgresHost, "the hostname of postgres database; "+ + cmd.configuration.StageGate.PostgresHost+" will be use if omitted") + + flagSet.IntVar(&cmd.port, "port", cmd.configuration.StageGate.PostgresPort, "the port number of postgres database; "+ + strconv.Itoa(configuration.StageGate.PostgresPort)+" will be use if omitted") + + flagSet.StringVar(&cmd.username, "username", "postgres", "the username of postgres database; "+ + "postgres will be use if omitted") + + flagSet.StringVar(&cmd.dbname, "dbname", "", "the database instance name of postgres database; "+ + "this is required for pinging the readiness of the database") + + flagSet.StringVar(&cmd.pwd, "password", "", "the user's password of postgres database; "+ + "this is required for pinging the readiness of the database") + + err := flagSet.Parse(args) + if err != nil { + return nil, fmt.Errorf("Unable to parse command: %s: %w", strings.Join(args, " "), err) + } + return &cmd, err +} + +func (c *cmd) Execute() (statusCode int, err error) { + c.loggingClient.Info(fmt.Sprintf("Security bootstrapper running %s", CommandName)) + + // test readiness of kong db + psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", + c.host, c.port, c.username, c.pwd, c.dbname) + + db, err := sql.Open("postgres", psqlInfo) + if err != nil { + return interfaces.StatusCodeExitWithError, fmt.Errorf("Failed to open sql driver connector: %v", err) + } + + defer func() { + _ = db.Close() + }() + + c.loggingClient.Debug("postgres sql driver connector opened") + + err = db.Ping() + if err != nil { + err = fmt.Errorf("failed to ping postgres database with provided db info: %v", err) + return interfaces.StatusCodeExitWithError, err + } + + c.loggingClient.Info("Postgres db is ready") + + // send to stdout so that the pinger can pick up the "ready" message + fmt.Fprintln(os.Stdout, "ready") + + return interfaces.StatusCodeExitNormal, nil +} + +func (c *cmd) GetCommandName() string { + return CommandName +} diff --git a/internal/security/bootstrapper/config/config.go b/internal/security/bootstrapper/config/config.go new file mode 100644 index 0000000000..c76ca2c056 --- /dev/null +++ b/internal/security/bootstrapper/config/config.go @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 config + +import ( + bootstrapConfig "github.com/edgexfoundry/go-mod-bootstrap/config" +) + +type ConfigurationStruct struct { + Writable WritableInfo + StageGate StageGateInfo +} + +type WritableInfo struct { + LogLevel string + InsecureSecrets bootstrapConfig.InsecureSecrets +} + +type StageGateInfo struct { + BootstrapperHost string + BootstrapPort int + VaultWorkerHost string + TokensReadyPort int + ReadyToRunPort int + RedisHost string + RedisPort int + RedisReadyPort int + ConsulHost string + ConsulPort int + ConsulReadyPort int + PostgresHost string + PostgresPort int + PostgresReadyPort int + ProxySetupHost string +} + +// UpdateFromRaw converts configuration received from the registry to a service-specific configuration struct which is +// then used to overwrite the service's existing configuration struct. +func (c *ConfigurationStruct) UpdateFromRaw(rawConfig interface{}) bool { + configuration, ok := rawConfig.(*ConfigurationStruct) + if ok { + *c = *configuration + } + return ok +} + +// EmptyWritablePtr returns a pointer to a service-specific empty WritableInfo struct. It is used by the bootstrap to +// provide the appropriate structure to registry.Client's WatchForChanges(). +func (c *ConfigurationStruct) EmptyWritablePtr() interface{} { + return &WritableInfo{} +} + +// UpdateWritableFromRaw converts configuration received from the registry to a service-specific WritableInfo struct +// which is then used to overwrite the service's existing configuration's WritableInfo struct. +func (c *ConfigurationStruct) UpdateWritableFromRaw(rawWritable interface{}) bool { + writable, ok := rawWritable.(*WritableInfo) + if ok { + c.Writable = *writable + } + return ok +} + +// GetBootstrap returns the configuration elements required by the bootstrap. Currently, a copy of the configuration +// data is returned. This is intended to be temporary -- since ConfigurationStruct drives the configuration.toml's +// structure -- until we can make backwards-breaking configuration.toml changes (which would consolidate these fields +// into an bootstrapConfig.BootstrapConfiguration struct contained within ConfigurationStruct). +func (c *ConfigurationStruct) GetBootstrap() bootstrapConfig.BootstrapConfiguration { + // temporary until we can make backwards-breaking configuration.toml change + return bootstrapConfig.BootstrapConfiguration{} +} + +// GetLogLevel returns the current ConfigurationStruct's log level. +func (c *ConfigurationStruct) GetLogLevel() string { + return c.Writable.LogLevel +} + +// GetRegistryInfo returns the RegistryInfo from the ConfigurationStruct. +func (c *ConfigurationStruct) GetRegistryInfo() bootstrapConfig.RegistryInfo { + return bootstrapConfig.RegistryInfo{} +} + +// GetDatabaseInfo returns a database information map. +func (c *ConfigurationStruct) GetDatabaseInfo() map[string]bootstrapConfig.Database { + return nil +} + +// GetInsecureSecrets returns the service's InsecureSecrets. +func (c *ConfigurationStruct) GetInsecureSecrets() bootstrapConfig.InsecureSecrets { + return nil +} diff --git a/internal/security/bootstrapper/container/container.go b/internal/security/bootstrapper/container/container.go new file mode 100644 index 0000000000..070c197165 --- /dev/null +++ b/internal/security/bootstrapper/container/container.go @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 container + +import ( + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/config" + "github.com/edgexfoundry/go-mod-bootstrap/di" +) + +// ConfigurationName contains the name of the config.ConfigurationStruct implementation in the DIC. +var ConfigurationName = di.TypeInstanceToName(config.ConfigurationStruct{}) + +// ConfigurationFrom helper function queries the DIC and returns the config.ConfigurationStruct implementation. +func ConfigurationFrom(get di.Get) *config.ConfigurationStruct { + return get(ConfigurationName).(*config.ConfigurationStruct) +} diff --git a/internal/security/bootstrapper/init.go b/internal/security/bootstrapper/init.go new file mode 100644 index 0000000000..055901e783 --- /dev/null +++ b/internal/security/bootstrapper/init.go @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 bootstrapper + +import ( + "context" + "flag" + "os" + "sync" + + bootstrapper "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/command" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/command/help" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/container" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/interfaces" + + bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/container" + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/startup" + "github.com/edgexfoundry/go-mod-bootstrap/di" +) + +// Bootstrap is to implement BootstrapHandler +type Bootstrap struct { + exitStatusCode int +} + +// NewBootstrap is to instantiate a Bootstrap instance +func NewBootstrap() *Bootstrap { + return &Bootstrap{} +} + +// BootstrapHandler fulfills the BootstrapHandler contract and performs initialization needed by the data service. +func (b *Bootstrap) BootstrapHandler(ctx context.Context, wg *sync.WaitGroup, _ startup.Timer, dic *di.Container) bool { + conf := container.ConfigurationFrom(dic.Get) + lc := bootstrapContainer.LoggingClientFrom(dic.Get) + + var command interfaces.Command + var err error + + var confdir string + flagSet := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + flagSet.StringVar(&confdir, "confdir", "", "") // handled by bootstrap; duplicated here to prevent arg parsing errors + err = flagSet.Parse(os.Args[1:]) + if err != nil { + lc.Error(err.Error()) + } + + subcommandArgs := []string{} + + commandName := flagSet.Arg(0) + if flag.NArg() > 0 { + subcommandArgs = append(subcommandArgs, flag.Args()...) + } + + switch commandName { + case help.CommandName: + command, err = help.NewCommand(lc, conf, subcommandArgs) + default: + command, err = bootstrapper.NewCommand(ctx, wg, lc, conf, subcommandArgs) + if command == nil { + lc.Error(err.Error()) + b.exitStatusCode = interfaces.StatusCodeNoOptionSelected + return false + } + } + + if err != nil { + lc.Error(err.Error()) + b.exitStatusCode = interfaces.StatusCodeExitWithError + return false + } + + exitStatusCode, err := command.Execute() + if err != nil { + lc.Error(err.Error()) + } + b.exitStatusCode = exitStatusCode + + wg.Done() + + return true +} diff --git a/internal/security/bootstrapper/interfaces/command.go b/internal/security/bootstrapper/interfaces/command.go new file mode 100644 index 0000000000..7a4fd7fce5 --- /dev/null +++ b/internal/security/bootstrapper/interfaces/command.go @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 interfaces + +const ( + // StatusCodeExitNormal exit code + StatusCodeExitNormal = 0 + // StatusCodeNoOptionSelected exit code + StatusCodeNoOptionSelected = 1 + // StatusCodeExitWithError is exit code for error + StatusCodeExitWithError = 2 +) + +// Command implement the Command pattern +type Command interface { + Execute() (statusCode int, err error) + GetCommandName() string +} diff --git a/internal/security/bootstrapper/main.go b/internal/security/bootstrapper/main.go new file mode 100644 index 0000000000..0eb557f103 --- /dev/null +++ b/internal/security/bootstrapper/main.go @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 bootstrapper + +import ( + "context" + "os" + + "github.com/gorilla/mux" + + "github.com/edgexfoundry/edgex-go/internal" + bootstrapper "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/command" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/config" + "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/container" + + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap" + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/interfaces" + "github.com/edgexfoundry/go-mod-bootstrap/bootstrap/startup" + "github.com/edgexfoundry/go-mod-bootstrap/di" +) + +const ( + securityBootstrapperServiceKey = "edgex-security-bootstrapper" +) + +func Main(ctx context.Context, cancel context.CancelFunc, _ *mux.Router, _ chan<- bool) { + // service key for this bootstrapper service + startupTimer := startup.NewStartUpTimer(securityBootstrapperServiceKey) + + // Common Command-line flags have been moved to command.CommonFlags, but this service doesn't use all + // the common flags so we are using our own implementation of the CommonFlags interface + f := bootstrapper.NewCommonFlags() + + f.Parse(os.Args[1:]) + + configuration := &config.ConfigurationStruct{} + dic := di.NewContainer(di.ServiceConstructorMap{ + container.ConfigurationName: func(get di.Get) interface{} { + return configuration + }, + }) + + serviceHandler := NewBootstrap() + + bootstrap.Run( + ctx, + cancel, + f, + securityBootstrapperServiceKey, + internal.ConfigStemSecurity+internal.ConfigMajorVersion, + configuration, + startupTimer, + dic, + []interfaces.BootstrapHandler{ + serviceHandler.BootstrapHandler, + }, + ) + + os.Exit(serviceHandler.exitStatusCode) +} diff --git a/internal/security/bootstrapper/tcp/client.go b/internal/security/bootstrapper/tcp/client.go new file mode 100644 index 0000000000..c6855ba519 --- /dev/null +++ b/internal/security/bootstrapper/tcp/client.go @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 tcp + +import ( + "fmt" + "net" + "strconv" + "strings" + "time" + + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" +) + +const ( + dialTimeoutDuration = 5 * time.Second +) + +// DialTcp will instantiate a new TCP dialer trying to connect to the TCP server specified by host and port +func DialTcp(host string, port int, lc logger.LoggingClient) error { + tcpHost := strings.TrimSpace(host) + if len(tcpHost) == 0 || port == 0 { + // log and ignore those + lc.Warn(fmt.Sprintf("Skipping: no TCP server specified, host=%s, port=%d", host, port)) + return nil + } + + tcpServerAddr := net.JoinHostPort(host, strconv.Itoa(port)) + + for { // keep trying until server connects + lc.Debug(fmt.Sprintf("Trying to connecting to TCP server at address: %s", tcpServerAddr)) + c, err := net.DialTimeout("tcp", tcpServerAddr, dialTimeoutDuration) + if err != nil { + if opErr, ok := err.(*net.OpError); ok && opErr.Op == "dial" { + lc.Info(fmt.Sprintf("TCP server %s may be not ready yet, retry in 1 second", tcpServerAddr)) + time.Sleep(time.Second) + continue + } else { + return err + } + } + defer func() { + _ = c.Close() + }() + + lc.Info(fmt.Sprintf("Connected with TCP server %s", tcpServerAddr)) + + break + } + return nil +} diff --git a/internal/security/bootstrapper/tcp/client_test.go b/internal/security/bootstrapper/tcp/client_test.go new file mode 100644 index 0000000000..2e9aeb0e83 --- /dev/null +++ b/internal/security/bootstrapper/tcp/client_test.go @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 tcp + +import ( + "testing" + "time" + + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + "github.com/stretchr/testify/require" +) + +func TestDialTcpClient(t *testing.T) { + lc := logger.MockLogger{} + errs := make(chan error, 1) + testListeningPort := 12333 + srv := NewTcpServer() + go func() { + errs <- srv.StartListener(testListeningPort, lc, "") + }() + + time.Sleep(time.Second) + + tests := []struct { + name string + host string + port int + }{ + {"Ignore empty host input", "", testListeningPort}, + {"Ignore port number 0", "localhost", 0}, + {"Ignore both empty host and 0 port number", "", 0}, + {"Dial the TCP server and port", "localhost", testListeningPort}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // dialTcp in goroutine so it won't block forever + go func() { + errs <- DialTcp(tt.host, tt.port, lc) + }() + + // give some time for connection + time.Sleep(2 * time.Second) + + require.NoError(t, <-errs) + }) + } +} diff --git a/internal/security/bootstrapper/tcp/listener.go b/internal/security/bootstrapper/tcp/listener.go new file mode 100644 index 0000000000..01e6262117 --- /dev/null +++ b/internal/security/bootstrapper/tcp/listener.go @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 tcp + +import ( + "bufio" + "fmt" + "net" + "strings" + "sync" + "time" + + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" +) + +const ( + connectionTimeout = 5 * time.Second +) + +type TcpServer struct { + accepted bool + lock *sync.Mutex +} + +func NewTcpServer() *TcpServer { + return &TcpServer{lock: &sync.Mutex{}} +} + +// StartListener will instantiate a new listener on port and optional host if it is not empty +// returns error if failed to create a listener on the port number +func (tcpSrv *TcpServer) StartListener(port int, lc logger.LoggingClient, host string) error { + lc.Debug(fmt.Sprintf("Starting listener on port %d ...", port)) + + trimmedHost := strings.TrimSpace(host) + doneSrv := fmt.Sprintf("%s:%d", trimmedHost, port) + + listener, err := net.Listen("tcp", doneSrv) + if err != nil { + // nolint: staticcheck + return fmt.Errorf("Failed to create TCP listener: %v", err) + } + + lc.Info(fmt.Sprintf("Security bootstrapper starts listening on tcp://%s", doneSrv)) + for { + conn, err := listener.Accept() + if err != nil { + lc.Error(fmt.Sprintf("found error when accepting connection: %v ! Will retry again", err)) + time.Sleep(time.Second) + continue + } + + tcpSrv.lock.Lock() + tcpSrv.accepted = true + tcpSrv.lock.Unlock() + + lc.Info(fmt.Sprintf("Accepted connection on port %d", port)) + + // once reached here, the connection is established, and consider the semaphore on this port is raised + go func(c *net.Conn) { + defer func() { + _ = (*c).Close() + }() + + if err := handleConnection(*c, lc); err != nil { + lc.Warn(fmt.Sprintf("failed to write trough connection: %v", err)) + } + + // intended process listener is done + lc.Debug(fmt.Sprintf("connection on port %d is done", port)) + }(&conn) + } +} + +// GetAcceptStatus returns the status of the server accept +func (tcpSrv *TcpServer) GetAcceptStatus() bool { + tcpSrv.lock.Lock() + status := tcpSrv.accepted + tcpSrv.lock.Unlock() + return status +} + +func handleConnection(conn net.Conn, lc logger.LoggingClient) error { + bufWriter := bufio.NewWriter(conn) + daytime := time.Now().String() + _ = conn.SetWriteDeadline(time.Now().Add(connectionTimeout)) + _, err := bufWriter.Write([]byte(daytime)) + + return err +} diff --git a/internal/security/bootstrapper/tcp/listener_test.go b/internal/security/bootstrapper/tcp/listener_test.go new file mode 100644 index 0000000000..458c5aa06c --- /dev/null +++ b/internal/security/bootstrapper/tcp/listener_test.go @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright 2021 Intel Corporation + * + * 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 tcp + +import ( + "fmt" + "testing" + "time" + + "github.com/edgexfoundry/go-mod-core-contracts/clients/logger" + "github.com/stretchr/testify/require" +) + +func TestStartListener(t *testing.T) { + lc := logger.MockLogger{} + errs := make(chan error, 1) + testPort := 12345 + srv := NewTcpServer() + go func() { + errs <- srv.StartListener(testPort, lc, "") + }() + + go func() { + errs <- DialTcp("127.0.0.1", testPort, lc) + }() + + // give some time for connection + time.Sleep(3 * time.Second) + + require.NoError(t, <-errs) + srv.lock.Lock() + defer srv.lock.Unlock() + accepted := srv.accepted + require.True(t, accepted) + +} + +func TestStartListenerAlreadyInUse(t *testing.T) { + lc := logger.MockLogger{} + errs := make(chan error, 1) + testPort := 12347 + srv1 := NewTcpServer() + go func() { + errs <- srv1.StartListener(testPort, lc, "") + }() + + // try to start another listener with the same port + // this will cause an error + srv2 := NewTcpServer() + go func() { + errs <- srv2.StartListener(testPort, lc, "") + }() + + time.Sleep(time.Second) + + go func() { + errs <- DialTcp("127.0.0.1", testPort, lc) + }() + + // give some time for connection + time.Sleep(time.Second) + + require.Error(t, <-errs) +} + +func TestStartListenerServerNotReady(t *testing.T) { + lc := logger.MockLogger{} + errs := make(chan error, 1) + testPort := 12349 + go func() { + errs <- DialTcp("127.0.0.1", testPort, lc) + }() + + time.Sleep(dialTimeoutDuration + time.Second) + + // since the tcp server is never up, the dial will block forever, + // so we set a timeout for the test to signal it is done + timeout := make(chan bool, 1) + go func() { + time.Sleep(2 * time.Second) + timeout <- true + }() + + select { + case err := <-errs: + require.NoError(t, err) + case <-timeout: + fmt.Println("Expected timed out") + close(timeout) + } +}