diff --git a/.circleci/ci/deployment.yml b/.circleci/ci/deployment.yml deleted file mode 100644 index 55bbc103..00000000 --- a/.circleci/ci/deployment.yml +++ /dev/null @@ -1,49 +0,0 @@ ---- -kind: Deployment -apiVersion: extensions/v1beta1 -metadata: - name: ${CIRCLE_PROJECT_REPONAME}-${IMAGE_BRANCH} - labels: - name: ${CIRCLE_PROJECT_REPONAME}-${IMAGE_BRANCH} - git.name: ${CIRCLE_PROJECT_REPONAME} - git.owner: ${CIRCLE_PROJECT_USERNAME} - git.branch: ${IMAGE_BRANCH} -spec: - # how many pods and indicate which strategy we want for rolling update - replicas: 1 - minReadySeconds: 10 - template: - metadata: - labels: - name: ${CIRCLE_PROJECT_REPONAME}-${IMAGE_BRANCH} - git.name: ${CIRCLE_PROJECT_REPONAME} - git.owner: ${CIRCLE_PROJECT_USERNAME} - git.branch: ${IMAGE_BRANCH} - annotations: - container.apparmor.security.beta.kubernetes.io/sftp: runtime/default - spec: - serviceAccountName: "${GKE_NAMESPACE_SA}" - containers: - - name: sftp - image: "${IMAGE_REPO}/${TARGET}:${IMAGE_TAG}" - imagePullPolicy: Always - ports: - - name: ssh - containerPort: 22 - resources: {} - livenessProbe: - tcpSocket: - port: ssh - initialDelaySeconds: 10 - periodSeconds: 10 - timeoutSeconds: 3 - failureThreshold: 2 - successThreshold: 1 - readinessProbe: - tcpSocket: - port: ssh - initialDelaySeconds: 10 - periodSeconds: 10 - timeoutSeconds: 3 - failureThreshold: 2 - successThreshold: 1 \ No newline at end of file diff --git a/.circleci/ci/service.yml b/.circleci/ci/service.yml deleted file mode 100644 index 78625750..00000000 --- a/.circleci/ci/service.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: ${CIRCLE_PROJECT_REPONAME}-${IMAGE_BRANCH} - labels: - name: ${CIRCLE_PROJECT_REPONAME}-${IMAGE_BRANCH} - git.name: ${CIRCLE_PROJECT_REPONAME} - git.owner: ${CIRCLE_PROJECT_USERNAME} - git.branch: ${IMAGE_BRANCH} -spec: - type: LoadBalancer - ports: - - name: ssh - port: 22 - targetPort: 22 - selector: - name: ${CIRCLE_PROJECT_REPONAME}-${IMAGE_BRANCH} - git.name: ${CIRCLE_PROJECT_REPONAME} - git.branch: ${IMAGE_BRANCH} - diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 97ad5cb7..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,107 +0,0 @@ -defaults: &defaults - working_directory: /rabbit-ssh - docker: - - image: google/cloud-sdk:alpine - environment: - TARGET: rabbit-ssh - -version: 2 -jobs: - build: - <<: *defaults - steps: - - checkout - - run: mkdir -p workspace - - run: echo -n "$CIRCLE_BUILD_NUM-$(git rev-parse --short HEAD)" > workspace/_tag - - run: - name: Set IMAGE_REPO ENV Var - command: | - echo "export IMAGE_REPO=gcr.io/${GOOGLE_PROJECT_ID}" >> $BASH_ENV - echo "export IMAGE_BRANCH=$(echo $CIRCLE_BRANCH | tr -s '/' '-' | tr '[:upper:]' '[:lower:]' | tr -cd '[[:alnum:]].-')" >> $BASH_ENV - echo "export IMAGE_TAG=$(cat workspace/_tag)" >> $BASH_ENV - source $BASH_ENV - - run: echo "Building $IMAGE_REPO/$TARGET:$(cat workspace/_tag)" - - setup_remote_docker - - run: - name: Add apk - command: | - apk add --no-cache gettext docker - - run: - name: Store Service Account - command: echo ${GCLOUD_SERVICE_KEY} > workspace/gcloud-service-key.json - - run: - name: Set gcloud auth - command: | - gcloud auth activate-service-account --key-file=workspace/gcloud-service-key.json - gcloud --quiet config set project ${GOOGLE_PROJECT_ID} - gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE} - - run: - name: Parse CI files - command: | - envsubst < ci/deployment.yml > workspace/k8sdeploy.yml - envsubst < ci/service.yml > workspace/k8ssvc.yml - - persist_to_workspace: - root: workspace - paths: - - gcloud-service-key.json - - _tag - - k8sdeploy.yml - - k8ssvc.yml - - restore_cache: - keys: - - v1-{{ .Branch }} - paths: - - /caches/app.tar - - run: - name: Load Docker image layer cache - command: | - set +o pipefail - docker load -i /caches/app.tar | true - - run: - name: Build application Docker image - command: | - docker build --cache-from=app -t $IMAGE_REPO/$TARGET:$(cat workspace/_tag) . - - run: - name: Push image to Google GCR - command: | - gcloud auth configure-docker --quiet - docker images - docker push $IMAGE_REPO/$TARGET:$(cat workspace/_tag) - - run: - name: Save Docker image layer cache - command: | - mkdir -p /caches - docker save -o /caches/app.tar $IMAGE_REPO/$TARGET:$(cat workspace/_tag) - - save_cache: - key: v1-{{ .Branch }}-{{ epoch }} - paths: - - /caches/app.tar - deploy: - <<: *defaults - steps: - - attach_workspace: - at: /tmp/workspace - - run: - name: Set gcloud auth - command: | - gcloud auth activate-service-account --key-file=/tmp/workspace/gcloud-service-key.json - gcloud --quiet config set project ${GOOGLE_PROJECT_ID} - gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE} - - run: - name: Install kubectl - command: | - gcloud --quiet container clusters get-credentials ${GOOGLE_CLUSTER_NAME} --region us-central1 --project ${GOOGLE_PROJECT_ID} - gcloud --quiet components install kubectl - - run: - name: Deploy to K8s - command: | - kubectl -n ${GKE_NAMESPACE} apply -f /tmp/workspace/k8ssvc.yml - kubectl -n ${GKE_NAMESPACE} apply -f /tmp/workspace/k8sdeploy.yml -workflows: - version: 2 - build_and_deploy: - jobs: - - build - - deploy: - requires: - - build \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ef0e7ab4..bb8b9b50 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -13,96 +13,90 @@ on: push: branches: - master + - latest + - develop-* env: PROJECT_ID: ${{ secrets.GKE_PROJECT }} - GKE_CLUSTER: ${{ secrets.GKE_CLUSTER }} # Add your cluster name here. - GKE_ZONE: ${{ secrets.GKE_ZONE }} # Add your cluster zone here. + GKE_CLUSTER: ${{ vars.GKE_CLUSTER }} # Add your cluster name here. + GKE_REGION: ${{ vars.GKE_REGION }} # Add your cluster zone here. DEPLOYMENT_NAME: ${{ secrets.DEPLOYMENT_NAME }} # Add your deployment name here. - IMAGE: ${{ secrets.IMAGE }} SLACK_NOTIFICACTION_URL: ${{ secrets.SLACK_NOTIFICACTION_URL }} SLACK_NOTIFICACTION_CHANNEL: ${{ secrets.SLACK_NOTIFICACTION_CHANNEL }} + AR_LOCATION: ${{ vars.AR_LOCATION }} + AR_REPOSITORY: ${{ vars.AR_REPOSITORY }} jobs: setup-build-publish-deploy: name: Setup, Build, Publish, and Deploy runs-on: ubuntu-latest - #environment: production + if: github.event_name == 'push' + environment: + name: ${{ github.ref_name }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Setup gcloud CLI - - uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7 + - id: 'auth' + uses: 'google-github-actions/auth@v2' + with: + credentials_json: '${{ secrets.GKE_SA_KEY }}' + + - name: 'Set up Cloud SDK' + uses: 'google-github-actions/setup-gcloud@v2' with: - service_account_key: ${{ secrets.GKE_SA_KEY }} project_id: ${{ secrets.GKE_PROJECT }} # Configure Docker to use the gcloud command-line tool as a credential # helper for authentication - run: |- - gcloud --quiet auth configure-docker - + gcloud --quiet auth configure-docker $AR_LOCATION-docker.pkg.dev # Get the GKE credentials so we can deploy to the cluster - - uses: google-github-actions/get-gke-credentials@fb08709ba27618c31c09e014e1d8364b02e5042e + - uses: google-github-actions/get-gke-credentials@v2 with: cluster_name: ${{ env.GKE_CLUSTER }} - location: ${{ env.GKE_ZONE }} - credentials: ${{ secrets.GKE_SA_KEY }} + location: ${{ env.GKE_REGION }} + project_id: ${{ secrets.GKE_PROJECT }} # Build the Docker image - name: Build run: |- docker build \ - --tag "gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA" \ + --tag "$AR_LOCATION-docker.pkg.dev/$PROJECT_ID/$AR_REPOSITORY/$GITHUB_REF_NAME:$GITHUB_SHA" \ --build-arg GITHUB_SHA="$GITHUB_SHA" \ --build-arg GITHUB_REF="$GITHUB_REF" \ . - # Push the Docker image to Google Container Registry - name: Publish run: |- - docker push "gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA" - + docker push "$AR_LOCATION-docker.pkg.dev/$PROJECT_ID/$AR_REPOSITORY/$GITHUB_REF_NAME:$GITHUB_SHA" # Set up kustomize - name: Set up Kustomize run: |- - curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v3.1.0/kustomize_3.1.0_linux_amd64 + curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.3.0/kustomize_v5.3.0_linux_amd64.tar.gz chmod u+x ./kustomize - # Deploy secret variables - run: | - sed -i.bak "s|CLUSTER_NAME_VALUE|${{ secrets.GKE_CLUSTER }}|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|CLUSTER_ENDPOINT_VALUE|${{ secrets.KUBERNETES_CLUSTER_ENDPOINT }}|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|CLUSTER_NAMESPACE_VALUE|${{ secrets.KUBERNETES_CLUSTER_NAMESPACE }}|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|CLUSTER_USER_TOKEN_VALUE|${{ secrets.KUBERNETES_CLUSTER_USER_TOKEN }}|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|CLUSTER_SERVICEACCOUNT_VALUE|${{ secrets.KUBERNETES_CLUSTER_SERVICEACCOUNT }}|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|CLUSTER_CERTIFICATE_VALUE|${{ secrets.KUBERNETES_CLUSTER_CERTIFICATE }}|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|CLUSTER_USER_SECRET_VALUE|${{ secrets.KUBERNETES_CLUSTER_USER_SECRET }}|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|CLUSTER_CONTEXT_VALUE|${{ secrets.KUBERNETES_CLUSTER_CONTEXT }}|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|ACCESS_TOKEN_VALUE|${{ secrets.ACCESS_TOKEN }}|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|IMAGE_VERSION|$GITHUB_SHA|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|GITHUB_ORG|$GITHUB_REPOSITORY_OWNER|g" bin/ci/service.yml - - run: | - sed -i.bak "s|GITHUB_ORG|$GITHUB_REPOSITORY_OWNER|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|GITHUB_BRANCH|$GITHUB_REF_NAME|g" bin/ci/service.yml - - run: | - sed -i.bak "s|GITHUB_BRANCH|$GITHUB_REF_NAME|g" bin/ci/deployment-v2.yml - - run: | - sed -i.bak "s|PROJECT_ID|$PROJECT_ID|g" bin/ci/deployment-v2.yml - + sed -i.bak "s|CLUSTER_NAME_VALUE|${{ vars.GKE_CLUSTER }}|g" ci/deployment-v2.yml + sed -i.bak "s|CLUSTER_ENDPOINT_VALUE|${{ secrets.KUBERNETES_CLUSTER_ENDPOINT }}|g" ci/deployment-v2.yml + sed -i.bak "s|CLUSTER_NAMESPACE_VALUE|${{ secrets.KUBERNETES_CLUSTER_NAMESPACE }}|g" ci/deployment-v2.yml + sed -i.bak "s|CLUSTER_USER_TOKEN_VALUE|${{ secrets.KUBERNETES_CLUSTER_USER_TOKEN }}|g" ci/deployment-v2.yml + sed -i.bak "s|CLUSTER_SERVICEACCOUNT_VALUE|${{ secrets.KUBERNETES_CLUSTER_SERVICEACCOUNT }}|g" ci/deployment-v2.yml + sed -i.bak "s|CLUSTER_CERTIFICATE_VALUE|${{ secrets.KUBERNETES_CLUSTER_CERTIFICATE }}|g" ci/deployment-v2.yml + sed -i.bak "s|CLUSTER_USER_SECRET_VALUE|${{ secrets.KUBERNETES_CLUSTER_USER_SECRET }}|g" ci/deployment-v2.yml + sed -i.bak "s|CLUSTER_CONTEXT_VALUE|${{ secrets.KUBERNETES_CLUSTER_CONTEXT }}|g" ci/deployment-v2.yml + sed -i.bak "s|ACCESS_TOKEN_VALUE|${{ secrets.ACCESS_TOKEN }}|g" ci/deployment-v2.yml + sed -i.bak "s|SLACK_NOTIFICACTION_URL_VALUE|${{ secrets.SLACK_NOTIFICACTION_URL }}|g" ci/deployment-v2.yml + sed -i.bak "s|SLACK_NOTIFICACTION_CHANNEL_VALUE|${{ secrets.SLACK_NOTIFICACTION_CHANNEL }}|g" ci/deployment-v2.yml + sed -i.bak "s|IMAGE_VERSION|$GITHUB_SHA|g" ci/deployment-v2.yml + sed -i.bak "s|GITHUB_ORG|$GITHUB_REPOSITORY_OWNER|g" ci/service.yml + sed -i.bak "s|GITHUB_ORG|$GITHUB_REPOSITORY_OWNER|g" ci/deployment-v2.yml + sed -i.bak "s|GITHUB_BRANCH|$GITHUB_REF_NAME|g" ci/service.yml + sed -i.bak "s|GITHUB_BRANCH|$GITHUB_REF_NAME|g" ci/deployment-v2.yml + sed -i.bak "s|PROJECT_ID|$PROJECT_ID|g" ci/deployment-v2.yml + sed -i.bak "s|AR_LOCATION|$AR_LOCATION|g" ci/deployment-v2.yml # Deploy the Docker image to the GKE cluster - run: | - kubectl apply -n ${{ secrets.KUBERNETES_CLUSTER_NAMESPACE }} -f bin/ci/service.yml && \ - kubectl apply -n ${{ secrets.KUBERNETES_CLUSTER_NAMESPACE }} -f bin/ci/deployment-v2.yml + kubectl apply -n ${{ secrets.KUBERNETES_CLUSTER_NAMESPACE }} -f ci/service.yml && \ + kubectl apply -n ${{ secrets.KUBERNETES_CLUSTER_NAMESPACE }} -f ci/deployment-v2.yml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 680927ba..87fd59fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -FROM node:14-alpine -ENV VERSION=v1.23.0 +FROM node:20-alpine +ENV VERSION=v1.29.0 ENV NODE_ENV=production ENV SERVICE_ENABLE_SSHD=true ENV SERVICE_ENABLE_API=true @@ -17,6 +17,16 @@ RUN apk add --no-cache git openssh nfs-utils rpcbind curl ca-certificates nano t && echo "America/New_York" > /etc/timezone \ && apk del tzdata +RUN curl -sSL https://sdk.cloud.google.com > /tmp/gcl && bash /tmp/gcl --install-dir=/root --disable-prompts + +ENV PATH $PATH:/root/google-cloud-sdk/bin + +#RUN gcloud components update kubectl + +RUN gcloud components install gke-gcloud-auth-plugin + +ENV USE_GKE_GCLOUD_AUTH_PLUGIN True + RUN \ npm -g install pm2 @@ -44,4 +54,4 @@ ENTRYPOINT ["/opt/sources/rabbitci/rabbit-ssh/bin/entrypoint.sh"] EXPOSE 22 -CMD [ "/usr/local/bin/node", "/usr/local/bin/pm2", "logs" ] +CMD [ "/usr/local/bin/node", "/usr/local/bin/pm2", "logs" ] \ No newline at end of file diff --git a/bin/controller.keys.js b/bin/controller.keys.js index 4510531f..bf6276c3 100755 --- a/bin/controller.keys.js +++ b/bin/controller.keys.js @@ -27,6 +27,10 @@ var _ = require('lodash'); module.exports.updateKeys = function updateKeys(options, taskCallback) { + let allowedRoles = process.env.ALLOW_SSH_ACCESS_ROLES || "admin,maintain,write"; + let productionBranch = process.env.PRODUCTION_BRANCH || "production"; + let allowedRolesForProd = process.env.ALLOW_SSH_ACCES_PROD_ROLES || "admin"; + taskCallback = 'function' === typeof taskCallback ? taskCallback : function taskCallback() { if (process.env.SLACK_NOTIFICACTION_URL && process.env.SLACK_NOTIFICACTION_URL.indexOf("https") === 0) { @@ -98,7 +102,7 @@ module.exports.updateKeys = function updateKeys(options, taskCallback) { let body = _.get(response, "data", {}); if (_.size(_.get(body, 'items', [])) === 0) { console.error("No response from container lookup at [%s].", _container_url); - console.error(" -err ", err); + console.error("Error fetching Kuberneter Pods", err.message); console.error(" -headers ", _.get(resp, 'headers')); //body = require('../static/fixtures/pods'); return false; @@ -125,10 +129,6 @@ module.exports.updateKeys = function updateKeys(options, taskCallback) { var _ssh_user = _labels['ci.rabbit.ssh.user']; - if (!_ssh_user) { - return; - } - // @todo May need to identify non-primary-branch apps here, or use a special label _applications[_ssh_user] = { _id: (_labels['git.owner'] || _labels['git_owner']) + '/' + (_labels['git.name'] || _labels['git_name']), @@ -177,19 +177,22 @@ module.exports.updateKeys = function updateKeys(options, taskCallback) { // get just the permissions, add users to application ('object' === typeof body && body.length > 0 ? body : []).forEach(function(thisUser) { - - _applications[data.sshUser].users[thisUser.login] = { - _id: thisUser.login, - permissions: thisUser.permissions - }; - _users[thisUser.login] = _users[thisUser.login] || []; - _users[thisUser.login].push(data._id); + // provide access only for users with roles: `maintain` and `admin` + if ((_.includes(_.split(allowedRoles, ","), thisUser.role_name) && (!data.sshUser.includes('.' + productionBranch)) || + _.includes(_.split(allowedRolesForProd, ","), thisUser.role_name))) { + _applications[data.sshUser].users[thisUser.login] = { + _id: thisUser.login, + permissions: thisUser.permissions + }; + _users[thisUser.login] = _users[thisUser.login] || []; + _users[thisUser.login].push(data._id); + } }); callback(); }) .catch(err => { - console.error(" -err ", err); + console.error(" Error fetching collaborators for " + data._id, err.message); callback(); }); @@ -199,7 +202,7 @@ module.exports.updateKeys = function updateKeys(options, taskCallback) { .catch(err => { console.log('getPods error: ', err.message); console.error("No response from container lookup at [%s].", _container_url); - console.error(" -err ", err); + console.error("Error processing: ", err.message); //console.error(" -headers ", _.get(resp, 'headers')); //body = require('../static/fixtures/pods'); return false; @@ -301,11 +304,11 @@ module.exports.updateKeys = function updateKeys(options, taskCallback) { containerName: _.get(_applications[appID], 'containers[0].containerName'), podName: _.get(_applications[appID], 'containers[0].podName'), user_data: userData._id, - CONNECTION_STRING: ['-n', _applications[appID].namespace, ' ', _.get(_applications[appID], 'containers[0].podName'), ' -c ', _.get(_applications[appID], 'containers[0].containerName')].join(' ') + CONNECTION_STRING: [_applications[appID].namespace, ' ', _.get(_applications[appID], 'containers[0].podName'), ' -c ', _.get(_applications[appID], 'containers[0].containerName')].join(' ') }; _.get(_allKeys, userData._id, []).forEach(function(thisUsersKey) { - writableKeys.push('environment="CONNECTION_STRING=' + _envs.CONNECTION_STRING + '" ' + thisUsersKey); + writableKeys.push('environment="ENV_VARS=' + _envs.CONNECTION_STRING + ';'+userData._id+'" ' + thisUsersKey); }) }); @@ -315,7 +318,7 @@ module.exports.updateKeys = function updateKeys(options, taskCallback) { fs.writeFile(_path, writableKeys.join("\n"), function(err) { if (err) { - return console.log(err); + return console.error(err.message); } debug("Wrote SSH Key file for [%s] identified as [%s] user.", appID, _applications[appID].sshUser); @@ -335,7 +338,7 @@ module.exports.updateKeys = function updateKeys(options, taskCallback) { fs.writeFile(_container_path, writableKeys.join("\n"), function(err) { if (err) { - return console.log(err); + return console.error(err.message); } console.log("Wrote SSH Key file for [%s] applications contianer [%s].", appID, _.get(singleContainer, 'podName')); @@ -357,7 +360,7 @@ module.exports.updateKeys = function updateKeys(options, taskCallback) { fs.readFile(_full_path, 'utf8', function(err, source) { if (err) { - return console.log(err); + return console.error(err.message); } var userFile = Mustache.render(source, { diff --git a/bin/controller.ssh.entrypoint.sh b/bin/controller.ssh.entrypoint.sh index eda1f9ea..af92d001 100755 --- a/bin/controller.ssh.entrypoint.sh +++ b/bin/controller.ssh.entrypoint.sh @@ -4,25 +4,27 @@ ## export _SERVICE=${USER}; +export CONNECTION_STRING=$(echo ${ENV_VARS} | cut -d ';' -f 1) +export USER_LOGIN=$(echo ${ENV_VARS} | cut -d ';' -f 2) -echo "[$(date)] Have a session for [${USER}] : ${SSH_ORIGINAL_COMMAND}, ${SSH_CLIENT}, ${SSH_CONNECTION} and [${CONNECTION_STRING}] command." >> /var/log/sshd.log +echo "[$(date)] Have a session for [${USER_LOGIN}] : ${USER}, ${SSH_ORIGINAL_COMMAND}, ${SSH_CLIENT}, ${SSH_CONNECTION} and [${CONNECTION_STRING}] command." >> /var/log/sshd.log ## SFTP. -if [[ "${SSH_ORIGINAL_COMMAND}" == "internal-sftp" ]]; then +if [[ ${SSH_ORIGINAL_COMMAND} == "internal-sftp" ]]; then echo "[$(date)] Have SFTP connection [${CONNECTION_STRING}] for [${USER}]." >> /var/log/sshd.log - /usr/local/bin/kubectl exec ${CONNECTION_STRING} -i -- /usr/lib/sftp-server + /usr/local/bin/kubectl exec -n ${CONNECTION_STRING} -i -- /usr/lib/sftp-server exit; fi -if [[ "${SSH_ORIGINAL_COMMAND}" == "/usr/lib/ssh/sftp-server" ]]; then +if [[ ${SSH_ORIGINAL_COMMAND} == "/usr/lib/ssh/sftp-server" ]]; then echo "[$(date)] Have SFTP connection [${CONNECTION_STRING}] for [${USER}]." >> /var/log/sshd.log - /usr/local/bin/kubectl exec ${CONNECTION_STRING} -i -- /usr/lib/sftp-server + /usr/local/bin/kubectl exec -n ${CONNECTION_STRING} -i -- /usr/lib/sftp-server exit; @@ -39,7 +41,7 @@ fi; ## Terminal, pipe into container. if [[ "x${SSH_ORIGINAL_COMMAND}" == "x" ]]; then - echo "kubectl exec ${CONNECTION_STRING} -ti /bin/bash" >> /var/log/sshd.log + echo "kubectl exec -n ${CONNECTION_STRING} -ti /bin/bash" >> /var/log/sshd.log #if [ "x${SSH_CONNECTION}" != "x" ]; then # export GIT_AUTHOR_EMAIL="${SSH_USER}"; @@ -57,7 +59,7 @@ if [[ "x${SSH_ORIGINAL_COMMAND}" == "x" ]]; then ## Log screen size. echo "[$(date)] Container [${USER}] has [${_COLUMNS}] columns and [${_ROWS}] rows." >> /var/log/sshd.log - _command="/usr/local/bin/kubectl exec $CONNECTION_STRING -ti -- /bin/bash" + _command="/usr/local/bin/kubectl exec -n $CONNECTION_STRING -ti -- /bin/bash" echo $_command >> /var/log/sshd.log @@ -66,4 +68,3 @@ if [[ "x${SSH_ORIGINAL_COMMAND}" == "x" ]]; then fi; exit; - diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index d1b63e31..4181b368 100755 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -44,17 +44,6 @@ if [[ -f /home/node/.kube/kuberentes-ca.crt ]]; then fi; -# npm install google-gax -# npm install axios -# npm install async -# npm install debug -# npm install express -# npm install lodash -# npm install mustache -# npm install md5 -# npm install firebase-admin -# npm install dot-object - npm install google-gax pm2 startOrReload /opt/sources/rabbitci/rabbit-ssh/static/ecosystem.config.js --silent @@ -62,5 +51,4 @@ pm2 startOrReload /opt/sources/rabbitci/rabbit-ssh/static/ecosystem.config.js -- npm install ## Command pass-through. -exec "$@" - +exec "$@" \ No newline at end of file diff --git a/bin/firebase.consume.js b/bin/firebase.consume.js index 049c6485..7de7e0ea 100644 --- a/bin/firebase.consume.js +++ b/bin/firebase.consume.js @@ -5,12 +5,7 @@ */ //var newrelic = require('newrelic') var admin = require("firebase-admin/lib/index"); -var request = require( 'request' ); -var execFile = require( 'child_process' ).execFile; -var request = require( 'request' ); -var async = require( 'async' ); var _ = require( 'lodash' ); -var controllerKeys = require( './controller.keys' ); exports.changeQueue = []; diff --git a/bin/rabbit.ssh.cli.js b/bin/rabbit.ssh.cli.js index 471302aa..632c1c48 100755 --- a/bin/rabbit.ssh.cli.js +++ b/bin/rabbit.ssh.cli.js @@ -8,7 +8,6 @@ var utility = require( '../lib/utility' ); var dot = require( 'dot-object' ); var _ = require( 'lodash' ); -var admin = utility.getFirebase(); utility.getCollection( 'container', 'meta/sshUser', function( error, data ) { diff --git a/bin/server.js b/bin/server.js index b5a84d42..c65925c8 100644 --- a/bin/server.js +++ b/bin/server.js @@ -1,6 +1,6 @@ /** * - * + * This is the SSH server that is used to connect to the Kubernetes cluster. * node ./server * */ @@ -12,7 +12,6 @@ const debug = require('debug')('ssh'); const app = express(); let utility = require('../lib/utility'); const md5 = require('md5'); -const { get } = require('lodash'); var accessToken = process.env.ACCESS_TOKEN; @@ -249,10 +248,6 @@ function serverOnline() { // detect non-kubernetes if (process.env.KUBERNETES_CLUSTER_ENDPOINT) { - //async function firestoreDoc() { - // var snap = await db.collection('github').doc("access").get(); - // accessToken = snap.data().token; - //console.log("token", accessToken); utility.updateKeys({ keysPath: '/etc/ssh/authorized_keys.d', passwordFile: '/etc/passwd', @@ -262,12 +257,9 @@ function serverOnline() { console.log('Updated state with [%s] SSH keys.', error || _.size(data.users)); app.set('sshUser', data.users); }); - //} - //firestoreDoc(); } if (process.env.SLACK_NOTIFICACTION_URL && process.env.SLACK_NOTIFICACTION_URL.indexOf("https") === 0) { - axios({ method: 'post', //you can set what request you want to be url: process.env.SLACK_NOTIFICACTION_URL, @@ -277,7 +269,6 @@ function serverOnline() { text: "Container " + (process.env.HOSTNAME || process.env.HOST) + " is up. ```kubectl -n rabbit-system logs -f " + (process.env.HOSTNAME || process.env.HOST) + "```" } }); - } else { console.log("process.env.SLACK_NOTIFICACTION_URL isn't set"); } diff --git a/changes.md b/changes.md index 4c82616f..100a5537 100644 --- a/changes.md +++ b/changes.md @@ -1,3 +1,39 @@ +### 0.2.9 +* Forwarded `sshd` logs to `container` logs + +### 0.2.8 +* updated `NodeJS` version to `20` +* updated `NodeJS Modules` to the latest versions +* fixed issue with `SFTP` connection +* prevented access for the `root` user +* added processing `SLACK_NOTIFICACTION_CHANNEL` and `SLACK_NOTIFICACTION_URL` environment variables in `GitHub Action` +* updated `GitHub Action` `Build and Deploy to GKE` +* added additional logging + +### 0.2.7 +* updated curl to 8.5.0 because of vulnerability +* access is allowed for the admin role in production + +### 0.2.6 +* updated kubectl version +* changed gcloud installation logic + +### 0.2.5 +* upgraded node to 18 +* added gcloud +* added gke-gcloud-auth-plugin +* moved docker image from GCR to AR + +### 0.2.4 +* Added an option to set roles by `ALLOW_SSH_ACCESS_ROLES` env. Set `admin`, `maintain`, `write` by default. +* Added the action to create a GitHub release. + +### 0.2.3 +* Prevent access to `production` containers. + +### 0.2.2 +* Prevent access to users with roles: `Read`, `Triage` and `Write`. Provide access only for roles: `Maintain` and `Admin`. + ### 0.2.1 * Fixed getPods endpoint for getting pods from all namespaces diff --git a/bin/ci/deployment-v2.yml b/ci/deployment-v2.yml similarity index 96% rename from bin/ci/deployment-v2.yml rename to ci/deployment-v2.yml index 26472bc7..18a55aaf 100644 --- a/bin/ci/deployment-v2.yml +++ b/ci/deployment-v2.yml @@ -29,7 +29,7 @@ spec: containers: - name: sftp #image: gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA - image: "gcr.io/PROJECT_ID/docker-sftp:IMAGE_VERSION" + image: "AR_LOCATION-docker.pkg.dev/PROJECT_ID/docker-sftp/GITHUB_BRANCH:IMAGE_VERSION" imagePullPolicy: Always ports: - name: ssh diff --git a/bin/ci/service.yml b/ci/service.yml similarity index 100% rename from bin/ci/service.yml rename to ci/service.yml diff --git a/lib/utility.js b/lib/utility.js index ff4a2cf8..64158602 100644 --- a/lib/utility.js +++ b/lib/utility.js @@ -1,8 +1,6 @@ var _ = require('lodash'); -var dot = require('dot-object'); var admin = require("firebase-admin"); var debug = require('debug')('ssh'); -var async = require('async'); /** * Converts Docker event message into a firebase-friendly container object. @@ -39,8 +37,6 @@ module.exports.normalizeMessage = function normalizeMessage(type, action, data) } if (_attributes && type === 'container') { - //_normalized.fields = dot.object(_attributes); - _.forEach(_attributes, function(value, key) { var _field = { diff --git a/package.json b/package.json index d4f31ec7..5da2ae13 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "docker-sftp", - "version": "0.2.1", - "description": "SSH tunnels to containers", + "version": "0.2.9", + "description": "SSH tunnels to Kubernetes containers", "main": "bin/server.js", "scripts": { "dev-start": "NODE_ENV=development pm2 start --watch --name server-ssh-dev bin/server.js", @@ -23,15 +23,15 @@ }, "homepage": "https://github.com/udx/docker-sftp#readme", "dependencies": { - "async": "^3.2.4", - "axios": "^1.2.2", - "google-gax": "^3.5.2", - "debug": "^3.2.6", - "dot-object": "^2.1.3", - "express": "^4.16.3", - "firebase-admin": "^11.4.1", + "async": "^3.2.5", + "axios": "^1.6.7", + "google-gax": "^4.3.0", + "debug": "^4.3.4", + "dot-object": "^2.1.4", + "express": "^4.18.2", + "firebase-admin": "^12.0.0", "lodash": "^4.17.21", - "mustache": "^2.3.0", + "mustache": "^4.2.0", "md5": "^2.3.0" } } \ No newline at end of file diff --git a/readme.md b/readme.md index f86397e0..435819e9 100644 --- a/readme.md +++ b/readme.md @@ -10,12 +10,14 @@ Run for debug: docker-compose up --build --renew-anon-volumes ``` +You can control an access to containers by adding `ALLOW_SSH_ACCESS_ROLES` env(str). +Set roles through coma you want to grant an access. +Roles `admin`, `maintain`, `write` are set by default. + ### Secrets -* GKE_PROJECT - GCP project ID -* GKE_SA_KEY - GCP SA key(json) -* GKE_CLUSTER - GKE cluster name -* GKE_ZONE - GKE cluster zone -* ACCESS_TOKEN - GitHub access token +* GKE_PROJECT - GCP Project ID +* GKE_SA_KEY - GCP Service Account key(in json format) +* ACCESS_TOKEN - GitHub Access Token * KUBERNETES_CLUSTER_ENDPOINT - domain or IP, without http/s * KUBERNETES_CLUSTER_SERVICEACCOUNT - k8s SA name * KUBERNETES_CLUSTER_CERTIFICATE - stringified PEM certificate @@ -26,6 +28,12 @@ docker-compose up --build --renew-anon-volumes * SLACK_NOTIFICACTION_URL - optional * SLACK_NOTIFICACTION_CHANNEL - optional +### Variables +* GKE_CLUSTER - GKE Cluster name +* GKE_REGION - GKE Cluster region +* AR_LOCATION - Artifact Registry location +* AR_REPOSITORY - Artifact Registry repository + ### Environment Variables * KUBERNETES_CLUSTER_ENDPOINT * KUBERNETES_CLUSTER_NAME diff --git a/static/ecosystem.config.js b/static/ecosystem.config.js index f1d839bd..174f9d95 100644 --- a/static/ecosystem.config.js +++ b/static/ecosystem.config.js @@ -4,9 +4,7 @@ if( process.env.SERVICE_ENABLE_SSHD === 'true' ) { module.exports.apps.push({ "script": "/usr/sbin/sshd", - "args": "-D -f /etc/ssh/sshd_config -E /var/log/sshd.log", - "out_file": "/var/log/sshd.log", - "error_file": "/var/log/sshd.log", + "args": "-D -f /etc/ssh/sshd_config -e", "name": "sshd", "merge_logs": true, "vizion": false, diff --git a/static/etc/ssh/sshd_config b/static/etc/ssh/sshd_config index edc79701..cf077d5e 100644 --- a/static/etc/ssh/sshd_config +++ b/static/etc/ssh/sshd_config @@ -37,7 +37,7 @@ HostKey /etc/ssh/ssh_host_rsa_key LogLevel INFO # Authentication: -PermitRootLogin yes +PermitRootLogin no StrictModes yes PubkeyAuthentication yes