├── .github └── workflows │ ├── build-image.yml │ └── deploy-infra.yml ├── README.md ├── app ├── distroless-giropops-senhas │ ├── .deepsource.toml │ ├── Dockerfile │ ├── LICENSE │ ├── app.py │ ├── requirements.txt │ ├── static │ │ ├── css │ │ │ ├── output.css │ │ │ └── styles.css │ │ ├── js │ │ │ └── main.js │ │ └── linuxtips-logo.png │ ├── tailwind.config.js │ └── templates │ │ ├── index.html │ │ └── lista_senhas.html ├── melange-giropops-senhas │ ├── .deepsource.toml │ ├── LICENSE │ ├── app.py │ ├── requirements.txt │ ├── static │ │ ├── css │ │ │ ├── output.css │ │ │ └── styles.css │ │ ├── js │ │ │ └── main.js │ │ └── linuxtips-logo.png │ ├── tailwind.config.js │ └── templates │ │ ├── index.html │ │ └── lista_senhas.html └── normal-giropops-senhas │ ├── .deepsource.toml │ ├── Dockerfile │ ├── LICENSE │ ├── app.py │ ├── requirements.txt │ ├── static │ ├── css │ │ ├── output.css │ │ └── styles.css │ ├── js │ │ └── main.js │ └── linuxtips-logo.png │ ├── tailwind.config.js │ └── templates │ ├── index.html │ └── lista_senhas.html ├── docs └── images │ ├── AWS-keys.png │ ├── Arquitetura-Infra.png │ ├── acesso-giropops.png │ ├── bot-firing.png │ ├── bot-resolved.png │ ├── build-distroless.png │ ├── build-melange.png │ ├── build-normal.png │ ├── camadas-builddistroless.png │ ├── camadas-buildmelange.png │ ├── camadas-buildnormal.png │ ├── crashloop.png │ ├── estrutura-helm.png │ ├── false-signature.png │ ├── get-top.png │ ├── giropops-image.png │ ├── grafana-alert.png │ ├── grafana-giropops.png │ ├── grafana-temporesposta.png │ ├── harbor-configs.png │ ├── helm-install-giropops.png │ ├── imagem-ajustada.png │ ├── keda-scaledown.png │ ├── keda-working.png │ ├── kyverno-bloqueios.png │ ├── locust-charts.png │ ├── locust-grafana1.png │ ├── locust-grafana2.png │ ├── locust-statics.png │ ├── locust-test.png │ ├── melange-keys.png │ ├── nginx-assinada.png │ ├── prom-target.png │ ├── redis-image.png │ ├── scale-deployment.png │ ├── scan-build-distroless.png │ ├── scan-build-melange.png │ ├── scan-build-normal.png │ └── set-env.png ├── main.tf ├── manifests ├── helm │ ├── giropops-app │ │ ├── giropops-chart-0.1.0.tgz │ │ ├── helm-giropops-app │ │ │ ├── Chart.yaml │ │ │ ├── templates │ │ │ │ ├── deployment.yaml │ │ │ │ ├── service.yaml │ │ │ │ └── serviceaccount.yaml │ │ │ └── values.yaml │ │ └── index.yaml │ └── ingress │ │ ├── helm-pick-cluster-ingress │ │ ├── Chart.yaml │ │ ├── templates │ │ │ └── ingress.yaml │ │ └── values.yaml │ │ ├── index.yaml │ │ └── ingress-templates-0.1.0.tgz ├── locust │ ├── locust-configmap.yaml │ ├── locust-deployment.yaml │ └── locust-service.yaml └── metrics-hpa │ ├── KEDA-scaledObjectRedis.yaml │ ├── hpa-giropops.senhas.yaml │ └── metrics-components.yaml ├── modules └── k8s_provisioner │ ├── ingress-deploy.yaml │ ├── main.tf │ ├── output.tf │ └── variables.tf ├── monitoring ├── grafana-alert.json ├── grafana-dash.json ├── prometheus-metrics.yaml └── proxy-prometheus.yaml ├── scripts └── install-helms.sh └── security ├── kyverno ├── allow-only-harbor-registry.yaml ├── disallow-root-user.yaml ├── disalow-default-ns.yaml ├── docker-cred.yaml ├── harbor-signature.yaml ├── require-probes.yaml └── verify-sensitive-vars.yaml └── melange ├── apko.yaml └── melange.yaml /.github/workflows/build-image.yml: -------------------------------------------------------------------------------- 1 | name: BUILDA IMAGEM 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | set-environment: 8 | runs-on: ubuntu-latest 9 | outputs: 10 | environment: ${{ env.environment }} 11 | steps: 12 | - name: Set environment 13 | id: set-environment 14 | run: | 15 | # Set Environment 16 | echo "BRANCH RECEBIDA: $GITHUB_REF_NAME" 17 | case "$GITHUB_REF_NAME" in 18 | "main") 19 | echo "environment=prd" >> $GITHUB_ENV ;; 20 | "stg") 21 | echo "environment=stg" >> $GITHUB_ENV ;; 22 | *) 23 | echo "environment=dev" >> $GITHUB_ENV ;; 24 | esac 25 | 26 | sign-docker-image: 27 | name: Sign Docker Image 28 | runs-on: ubuntu-20.04 29 | needs: set-environment 30 | steps: 31 | - name: Checkout code 32 | uses: actions/checkout@v4 33 | 34 | - name: Import Certificates 35 | run: | 36 | echo "${{ secrets.COSIGN_KEY }}" | base64 -d > ${GITHUB_WORKSPACE}/app/melange-giropops-senhas/cosign.key 37 | echo "${{ secrets.MELANGE_KEY }}" | base64 -d > ${GITHUB_WORKSPACE}/app/melange-giropops-senhas/melange.rsa 38 | echo "${{ secrets.MELANGE_PUB }}" | base64 -d > ${GITHUB_WORKSPACE}/app/melange-giropops-senhas/melange.rsa.pub 39 | 40 | - name: Build Docker image 41 | run: | 42 | cd ${GITHUB_WORKSPACE}/app/melange-giropops-senhas/ 43 | ls -lha 44 | cp ${GITHUB_WORKSPACE}/security/melange/melange.yaml ${GITHUB_WORKSPACE}/security/melange/apko.yaml . 45 | docker run --rm --privileged -v "${PWD}":/work -w /work \ 46 | cgr.dev/chainguard/melange build melange.yaml \ 47 | --arch host \ 48 | --signing-key melange.rsa 49 | 50 | docker run --rm -v "$PWD":/work -w /work \ 51 | cgr.dev/chainguard/apko build apko.yaml \ 52 | harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas:${{ needs.set-environment.outputs.environment }} \ 53 | giropops-senhas.tar -k melange.rsa.pub --arch host 54 | 55 | 56 | docker load < giropops-senhas.tar 57 | 58 | - name: Login to Harbor 59 | run: | 60 | docker login -u fabiobartoli https://harbor.fabiobartoli.com.br/ -p ${{ secrets.HARBOR_PASS }} 61 | 62 | - name: Install Cosign 63 | run: | 64 | curl -sL https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 --output cosign 65 | chmod +x cosign 66 | sudo mv cosign /usr/local/bin/cosign 67 | 68 | - name: Tag, Sign and Push image 69 | run: | 70 | cd ${GITHUB_WORKSPACE}/app/melange-giropops-senhas/ 71 | docker tag harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas:${{ needs.set-environment.outputs.environment }}-amd64 harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas:${{ needs.set-environment.outputs.environment }} 72 | docker push harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas:${{ needs.set-environment.outputs.environment }} 73 | cosign sign --key cosign.key harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas:${{ needs.set-environment.outputs.environment }} --yes 74 | docker tag harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas:${{ needs.set-environment.outputs.environment }} harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas:${{ GITHUB.SHA }} 75 | docker push harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas:${{ GITHUB.SHA }} 76 | if [ "${{ needs.set-environment.outputs.environment }}" = "prd" ]; then 77 | docker tag harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas:${{ needs.set-environment.outputs.environment }} harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas:latest 78 | docker push harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas:latest 79 | fi -------------------------------------------------------------------------------- /.github/workflows/deploy-infra.yml: -------------------------------------------------------------------------------- 1 | name: CRIA INFRA - K8S 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | action: 7 | description: 'Escolha uma ação' 8 | required: true 9 | default: 'apply' 10 | type: choice 11 | options: 12 | - apply 13 | - destroy 14 | 15 | env: 16 | TF_VAR_access_key: ${{ secrets.ACCESS_KEY }} 17 | TF_VAR_secret_key: ${{ secrets.SECRET_KEY }} 18 | AWS_ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY }} 19 | AWS_SECRET_ACCESS_KEY: ${{ secrets.SECRET_KEY }} 20 | AWS_REGION: "us-east-1" 21 | 22 | jobs: 23 | k8s-provisioner: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v4 28 | 29 | - name: Loading Credentials 30 | run: | 31 | echo "${{ secrets.PUBLIC_KEY }}" > id_rsa.pub 32 | echo "${{ secrets.PRIVATE_KEY }}" > id_rsa 33 | chmod 600 id_rsa 34 | 35 | - name: Install Terraform 36 | uses: hashicorp/setup-terraform@v3 37 | 38 | - name: Terraform Init 39 | run: terraform init 40 | 41 | - name: Terraform Validate 42 | id: validate 43 | run: terraform validate -no-color 44 | 45 | - name: Terraform Apply or Destroy 46 | run: | 47 | if [ "${{ github.event.inputs.action }}" == "apply" ]; then 48 | terraform apply --auto-approve 49 | aws ssm delete-parameter --name "k8s_join_command" 50 | aws ssm delete-parameter --name "k8s_kubeconfig" 51 | elif [ "${{ github.event.inputs.action }}" == "destroy" ]; then 52 | terraform destroy --auto-approve 53 | else 54 | echo "Invalid action: ${{ github.event.inputs.action }}" 55 | exit 1 56 | fi 57 | 58 | - name: Print Terraform Outputs 59 | if: steps.terraform-apply-or-destroy.outcome != 'failure' && github.event.inputs.action == 'apply' 60 | run: | 61 | echo "Control Plane Public IP:" 62 | terraform output control_plane_public_ip 63 | echo "Worker Public IPs:" 64 | terraform output worker_public_ips -------------------------------------------------------------------------------- /app/distroless-giropops-senhas/.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "javascript" 5 | 6 | [[analyzers]] 7 | name = "python" 8 | 9 | [analyzers.meta] 10 | runtime_version = "3.x.x" -------------------------------------------------------------------------------- /app/distroless-giropops-senhas/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cgr.dev/chainguard/python:latest-dev AS python-builder 2 | WORKDIR /app 3 | COPY requirements.txt . 4 | RUN pip install --no-cache-dir --user -r requirements.txt \ 5 | && chmod 777 /home/nonroot/.local/lib/python3.12/site-packages/flask 6 | 7 | FROM cgr.dev/chainguard/python:latest 8 | WORKDIR /opt/app 9 | ENV REDIS_HOST=localhost 10 | COPY app.py . 11 | COPY static/ static/ 12 | COPY templates/ templates/ 13 | COPY --from=python-builder /home/nonroot/.local/lib/python3.12/site-packages /home/nonroot/.local/lib/python3.12/site-packages 14 | COPY --from=python-builder /home/nonroot/.local/bin /home/nonroot/.local/bin 15 | ENV PATH=$PATH:/home/nonroot/.local/bin 16 | EXPOSE 5000 17 | ENTRYPOINT ["flask", "run", "--host=0.0.0.0"] 18 | -------------------------------------------------------------------------------- /app/distroless-giropops-senhas/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, jsonify 2 | import logging 3 | import os 4 | from prometheus_client import Counter, Histogram, Gauge, start_http_server, generate_latest 5 | import random 6 | import redis 7 | import socket 8 | import string 9 | import time 10 | 11 | app = Flask(__name__, 12 | template_folder='./templates', 13 | static_folder='./static') 14 | 15 | redis_host = os.environ.get('REDIS_HOST', 'redis-service') 16 | redis_port = 6379 17 | redis_password = "" 18 | 19 | senha_gerada_counter = Counter('senha_gerada', 'Contador de senhas geradas') 20 | senha_gerada_numeros_counter = Counter('senha_gerada_numeros', 'Contador de senhas geradas com números') 21 | senha_gerada_caracteres_especiais_counter = Counter('senha_gerada_caracteres_especiais', 'Contador de senhas geradas com caracteres especiais') 22 | senha_gerada_sem_caracteres_especiais_counter = Counter('senha_gerada_sem_caracteres_especiais', 'Contador de senhas geradas sem caracteres especiais') 23 | senha_gerada_sem_numeros_counter = Counter('senha_gerada_sem_numeros', 'Contador de senhas geradas sem números') 24 | redis_connection_error_counter = Counter('redis_connection_errors', 'Contador de erros de conexão com Redis') 25 | tempo_gerar_senha_histogram = Histogram('tempo_gerar_senha', 'Tempo para gerar uma senha') 26 | tempo_resposta_api_histogram = Histogram('tempo_resposta_api', 'Tempo de resposta da API') 27 | api_erro_counter = Counter('api_errors', 'Contador de erros de API', ['endpoint', 'status_code']) 28 | tamanho_fila_senhas_gauge = Gauge('tamanho_fila_senhas', 'Tamanho da fila de senhas no Redis') 29 | 30 | try: 31 | r = redis.StrictRedis(host=redis_host, port=redis_port, password=redis_password, decode_responses=True) 32 | r.ping() 33 | except redis.ConnectionError: 34 | logging.error("Erro ao conectar ao Redis") 35 | redis_connection_error_counter.inc() 36 | r = None 37 | 38 | def criar_senha(tamanho, incluir_numeros, incluir_caracteres_especiais): 39 | caracteres = string.ascii_letters 40 | 41 | if incluir_numeros: 42 | caracteres += string.digits 43 | senha_gerada_numeros_counter.inc() 44 | 45 | if incluir_caracteres_especiais: 46 | caracteres += string.punctuation 47 | senha_gerada_caracteres_especiais_counter.inc() 48 | else: 49 | senha_gerada_sem_caracteres_especiais_counter.inc() 50 | 51 | senha = ''.join(random.choices(caracteres, k=tamanho)) 52 | 53 | if not any(char.isdigit() for char in senha): 54 | senha_gerada_sem_numeros_counter.inc() 55 | 56 | return senha 57 | 58 | @app.route('/', methods=['GET', 'POST']) 59 | def index(): 60 | if request.method == 'POST': 61 | tamanho = int(request.form.get('tamanho', 8)) 62 | incluir_numeros = request.form.get('incluir_numeros') == 'on' 63 | incluir_caracteres_especiais = request.form.get('incluir_caracteres_especiais') == 'on' 64 | 65 | #Medir time de gerar uma senha: 66 | start_time = time.time() 67 | senha = criar_senha(tamanho, incluir_numeros, incluir_caracteres_especiais) 68 | tempo_gerar_senha_histogram.observe(time.time() - start_time) 69 | 70 | if r: 71 | r.lpush("senhas", senha) 72 | senha_gerada_counter.inc() 73 | 74 | senhas = r.lrange("senhas", 0, 9) if r else [] 75 | if senhas: 76 | senhas_geradas = [{"id": index + 1, "senha": senha} for index, senha in enumerate(senhas)] 77 | return render_template('index.html', senhas_geradas=senhas_geradas, senha=senhas_geradas[0]['senha'] or '' ) 78 | return render_template('index.html') 79 | 80 | 81 | @app.route('/api/gerar-senha', methods=['POST']) 82 | @tempo_resposta_api_histogram.time() 83 | def gerar_senha_api(): 84 | dados = request.get_json() 85 | 86 | tamanho = int(dados.get('tamanho', 8)) 87 | incluir_numeros = dados.get('incluir_numeros', False) 88 | incluir_caracteres_especiais = dados.get('incluir_caracteres_especiais', False) 89 | 90 | start_time = time.time() 91 | try: 92 | senha = criar_senha(tamanho, incluir_numeros, incluir_caracteres_especiais) 93 | tempo_gerar_senha_histogram.observe(time.time() - start_time) 94 | if r: 95 | r.lpush("senhas", senha) 96 | senha_gerada_counter.inc() 97 | return jsonify({"senha": senha}), 200 98 | except Exception as e: 99 | api_erro_counter.labels(endpoint='/api/gerar-senha', status_code='500').inc() 100 | logging.error(f"Erro na API /api/gerar-senha: {str(e)}", exc_info=True) 101 | return jsonify({"error": "Erro ao gerar senha"}), 500 102 | 103 | @app.route('/api/senhas', methods=['GET']) 104 | @tempo_resposta_api_histogram.time() 105 | def listar_senhas(): 106 | try: 107 | senhas = r.lrange("senhas", 0, 9) if r else [] 108 | tamanho_fila_senhas_gauge.set(len(senhas)) 109 | 110 | resposta = [{"id": index + 1, "senha": senha} for index, senha in enumerate(senhas)] 111 | return jsonify(resposta) 112 | except Exception as e: 113 | api_erro_counter.labels(endpoint='/api/senhas', status_code='500').inc() 114 | logging.error(f"Erro na API /api/senhas: {str(e)}", exc_info=True) 115 | return jsonify({"error": "Erro ao listar senhas"}), 500 116 | 117 | @app.route('/metrics') 118 | def metrics(): 119 | return generate_latest() 120 | 121 | def start_prometheus_server(port): 122 | try: 123 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 124 | s.settimeout(1) 125 | if s.connect_ex(('0.0.0.0', port)) == 0: 126 | logging.error(f"Porta {port} já está em uso. Não será possível iniciar Prometheus.") 127 | return False 128 | 129 | start_http_server(port) 130 | return True 131 | except Exception as e: 132 | logging.error(f"Erro ao tentar iniciar Prometheus: {str(e)}", exc_info=True) 133 | return False 134 | 135 | if __name__ == '__main__': 136 | logging.basicConfig(filename='tmp/error.log', level=logging.DEBUG) 137 | port = 8089 138 | app.run(host='0.0.0.0', debug=False) 139 | -------------------------------------------------------------------------------- /app/distroless-giropops-senhas/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==3.0.3 2 | redis==4.5.4 3 | prometheus-client==0.16.0 -------------------------------------------------------------------------------- /app/distroless-giropops-senhas/static/css/output.css: -------------------------------------------------------------------------------- 1 | /* styles.css */ 2 | 3 | /* ! tailwindcss v3.2.7 | MIT License | https://tailwindcss.com */ 4 | 5 | /* 6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) 7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) 8 | */ 9 | 10 | *, 11 | ::before, 12 | ::after { 13 | box-sizing: border-box; 14 | /* 1 */ 15 | border-width: 0; 16 | /* 2 */ 17 | border-style: solid; 18 | /* 2 */ 19 | border-color: #e5e7eb; 20 | /* 2 */ 21 | } 22 | 23 | ::before, 24 | ::after { 25 | --tw-content: ''; 26 | } 27 | 28 | /* 29 | 1. Use a consistent sensible line-height in all browsers. 30 | 2. Prevent adjustments of font size after orientation changes in iOS. 31 | 3. Use a more readable tab size. 32 | 4. Use the user's configured `sans` font-family by default. 33 | 5. Use the user's configured `sans` font-feature-settings by default. 34 | */ 35 | 36 | html { 37 | line-height: 1.5; 38 | /* 1 */ 39 | -webkit-text-size-adjust: 100%; 40 | /* 2 */ 41 | -moz-tab-size: 4; 42 | /* 3 */ 43 | -o-tab-size: 4; 44 | tab-size: 4; 45 | /* 3 */ 46 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 47 | /* 4 */ 48 | font-feature-settings: normal; 49 | /* 5 */ 50 | } 51 | 52 | /* 53 | 1. Remove the margin in all browsers. 54 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. 55 | */ 56 | 57 | body { 58 | margin: 0; 59 | /* 1 */ 60 | line-height: inherit; 61 | /* 2 */ 62 | } 63 | 64 | /* 65 | 1. Add the correct height in Firefox. 66 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) 67 | 3. Ensure horizontal rules are visible by default. 68 | */ 69 | 70 | hr { 71 | height: 0; 72 | /* 1 */ 73 | color: inherit; 74 | /* 2 */ 75 | border-top-width: 1px; 76 | /* 3 */ 77 | } 78 | 79 | /* 80 | Add the correct text decoration in Chrome, Edge, and Safari. 81 | */ 82 | 83 | abbr:where([title]) { 84 | -webkit-text-decoration: underline dotted; 85 | text-decoration: underline dotted; 86 | } 87 | 88 | /* 89 | Remove the default font size and weight for headings. 90 | */ 91 | 92 | h1, 93 | h2, 94 | h3, 95 | h4, 96 | h5, 97 | h6 { 98 | font-size: inherit; 99 | font-weight: inherit; 100 | } 101 | 102 | /* 103 | Reset links to optimize for opt-in styling instead of opt-out. 104 | */ 105 | 106 | a { 107 | color: inherit; 108 | text-decoration: inherit; 109 | } 110 | 111 | /* 112 | Add the correct font weight in Edge and Safari. 113 | */ 114 | 115 | b, 116 | strong { 117 | font-weight: bolder; 118 | } 119 | 120 | /* 121 | 1. Use the user's configured `mono` font family by default. 122 | 2. Correct the odd `em` font sizing in all browsers. 123 | */ 124 | 125 | code, 126 | kbd, 127 | samp, 128 | pre { 129 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 130 | /* 1 */ 131 | font-size: 1em; 132 | /* 2 */ 133 | } 134 | 135 | /* 136 | Add the correct font size in all browsers. 137 | */ 138 | 139 | small { 140 | font-size: 80%; 141 | } 142 | 143 | /* 144 | Prevent `sub` and `sup` elements from affecting the line height in all browsers. 145 | */ 146 | 147 | sub, 148 | sup { 149 | font-size: 75%; 150 | line-height: 0; 151 | position: relative; 152 | vertical-align: baseline; 153 | } 154 | 155 | sub { 156 | bottom: -0.25em; 157 | } 158 | 159 | sup { 160 | top: -0.5em; 161 | } 162 | 163 | /* 164 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) 165 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) 166 | 3. Remove gaps between table borders by default. 167 | */ 168 | 169 | table { 170 | text-indent: 0; 171 | /* 1 */ 172 | border-color: inherit; 173 | /* 2 */ 174 | border-collapse: collapse; 175 | /* 3 */ 176 | } 177 | 178 | /* 179 | 1. Change the font styles in all browsers. 180 | 2. Remove the margin in Firefox and Safari. 181 | 3. Remove default padding in all browsers. 182 | */ 183 | 184 | button, 185 | input, 186 | optgroup, 187 | select, 188 | textarea { 189 | font-family: inherit; 190 | /* 1 */ 191 | font-size: 100%; 192 | /* 1 */ 193 | font-weight: inherit; 194 | /* 1 */ 195 | line-height: inherit; 196 | /* 1 */ 197 | color: inherit; 198 | /* 1 */ 199 | margin: 0; 200 | /* 2 */ 201 | padding: 0; 202 | /* 3 */ 203 | } 204 | 205 | /* 206 | Remove the inheritance of text transform in Edge and Firefox. 207 | */ 208 | 209 | button, 210 | select { 211 | text-transform: none; 212 | } 213 | 214 | /* 215 | 1. Correct the inability to style clickable types in iOS and Safari. 216 | 2. Remove default button styles. 217 | */ 218 | 219 | button, 220 | [type='button'], 221 | [type='reset'], 222 | [type='submit'] { 223 | -webkit-appearance: button; 224 | /* 1 */ 225 | background-color: transparent; 226 | /* 2 */ 227 | background-image: none; 228 | /* 2 */ 229 | } 230 | 231 | /* 232 | Use the modern Firefox focus style for all focusable elements. 233 | */ 234 | 235 | :-moz-focusring { 236 | outline: auto; 237 | } 238 | 239 | /* 240 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) 241 | */ 242 | 243 | :-moz-ui-invalid { 244 | box-shadow: none; 245 | } 246 | 247 | /* 248 | Add the correct vertical alignment in Chrome and Firefox. 249 | */ 250 | 251 | progress { 252 | vertical-align: baseline; 253 | } 254 | 255 | /* 256 | Correct the cursor style of increment and decrement buttons in Safari. 257 | */ 258 | 259 | ::-webkit-inner-spin-button, 260 | ::-webkit-outer-spin-button { 261 | height: auto; 262 | } 263 | 264 | /* 265 | 1. Correct the odd appearance in Chrome and Safari. 266 | 2. Correct the outline style in Safari. 267 | */ 268 | 269 | [type='search'] { 270 | -webkit-appearance: textfield; 271 | /* 1 */ 272 | outline-offset: -2px; 273 | /* 2 */ 274 | } 275 | 276 | /* 277 | Remove the inner padding in Chrome and Safari on macOS. 278 | */ 279 | 280 | ::-webkit-search-decoration { 281 | -webkit-appearance: none; 282 | } 283 | 284 | /* 285 | 1. Correct the inability to style clickable types in iOS and Safari. 286 | 2. Change font properties to `inherit` in Safari. 287 | */ 288 | 289 | ::-webkit-file-upload-button { 290 | -webkit-appearance: button; 291 | /* 1 */ 292 | font: inherit; 293 | /* 2 */ 294 | } 295 | 296 | /* 297 | Add the correct display in Chrome and Safari. 298 | */ 299 | 300 | summary { 301 | display: list-item; 302 | } 303 | 304 | /* 305 | Removes the default spacing and border for appropriate elements. 306 | */ 307 | 308 | blockquote, 309 | dl, 310 | dd, 311 | h1, 312 | h2, 313 | h3, 314 | h4, 315 | h5, 316 | h6, 317 | hr, 318 | figure, 319 | p, 320 | pre { 321 | margin: 0; 322 | } 323 | 324 | fieldset { 325 | margin: 0; 326 | padding: 0; 327 | } 328 | 329 | legend { 330 | padding: 0; 331 | } 332 | 333 | ol, 334 | ul, 335 | menu { 336 | list-style: none; 337 | margin: 0; 338 | padding: 0; 339 | } 340 | 341 | /* 342 | Prevent resizing textareas horizontally by default. 343 | */ 344 | 345 | textarea { 346 | resize: vertical; 347 | } 348 | 349 | /* 350 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) 351 | 2. Set the default placeholder color to the user's configured gray 400 color. 352 | */ 353 | 354 | input::-moz-placeholder, textarea::-moz-placeholder { 355 | opacity: 1; 356 | /* 1 */ 357 | color: #9ca3af; 358 | /* 2 */ 359 | } 360 | 361 | input::placeholder, 362 | textarea::placeholder { 363 | opacity: 1; 364 | /* 1 */ 365 | color: #9ca3af; 366 | /* 2 */ 367 | } 368 | 369 | /* 370 | Set the default cursor for buttons. 371 | */ 372 | 373 | button, 374 | [role="button"] { 375 | cursor: pointer; 376 | } 377 | 378 | /* 379 | Make sure disabled buttons don't get the pointer cursor. 380 | */ 381 | 382 | :disabled { 383 | cursor: default; 384 | } 385 | 386 | /* 387 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) 388 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) 389 | This can trigger a poorly considered lint error in some tools but is included by design. 390 | */ 391 | 392 | img, 393 | svg, 394 | video, 395 | canvas, 396 | audio, 397 | iframe, 398 | embed, 399 | object { 400 | display: block; 401 | /* 1 */ 402 | vertical-align: middle; 403 | /* 2 */ 404 | } 405 | 406 | /* 407 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) 408 | */ 409 | 410 | img, 411 | video { 412 | max-width: 100%; 413 | height: auto; 414 | } 415 | 416 | /* Make elements with the HTML hidden attribute stay hidden by default */ 417 | 418 | [hidden] { 419 | display: none; 420 | } 421 | 422 | *, ::before, ::after { 423 | --tw-border-spacing-x: 0; 424 | --tw-border-spacing-y: 0; 425 | --tw-translate-x: 0; 426 | --tw-translate-y: 0; 427 | --tw-rotate: 0; 428 | --tw-skew-x: 0; 429 | --tw-skew-y: 0; 430 | --tw-scale-x: 1; 431 | --tw-scale-y: 1; 432 | --tw-pan-x: ; 433 | --tw-pan-y: ; 434 | --tw-pinch-zoom: ; 435 | --tw-scroll-snap-strictness: proximity; 436 | --tw-ordinal: ; 437 | --tw-slashed-zero: ; 438 | --tw-numeric-figure: ; 439 | --tw-numeric-spacing: ; 440 | --tw-numeric-fraction: ; 441 | --tw-ring-inset: ; 442 | --tw-ring-offset-width: 0px; 443 | --tw-ring-offset-color: #fff; 444 | --tw-ring-color: rgb(59 130 246 / 0.5); 445 | --tw-ring-offset-shadow: 0 0 #0000; 446 | --tw-ring-shadow: 0 0 #0000; 447 | --tw-shadow: 0 0 #0000; 448 | --tw-shadow-colored: 0 0 #0000; 449 | --tw-blur: ; 450 | --tw-brightness: ; 451 | --tw-contrast: ; 452 | --tw-grayscale: ; 453 | --tw-hue-rotate: ; 454 | --tw-invert: ; 455 | --tw-saturate: ; 456 | --tw-sepia: ; 457 | --tw-drop-shadow: ; 458 | --tw-backdrop-blur: ; 459 | --tw-backdrop-brightness: ; 460 | --tw-backdrop-contrast: ; 461 | --tw-backdrop-grayscale: ; 462 | --tw-backdrop-hue-rotate: ; 463 | --tw-backdrop-invert: ; 464 | --tw-backdrop-opacity: ; 465 | --tw-backdrop-saturate: ; 466 | --tw-backdrop-sepia: ; 467 | } 468 | 469 | ::backdrop { 470 | --tw-border-spacing-x: 0; 471 | --tw-border-spacing-y: 0; 472 | --tw-translate-x: 0; 473 | --tw-translate-y: 0; 474 | --tw-rotate: 0; 475 | --tw-skew-x: 0; 476 | --tw-skew-y: 0; 477 | --tw-scale-x: 1; 478 | --tw-scale-y: 1; 479 | --tw-pan-x: ; 480 | --tw-pan-y: ; 481 | --tw-pinch-zoom: ; 482 | --tw-scroll-snap-strictness: proximity; 483 | --tw-ordinal: ; 484 | --tw-slashed-zero: ; 485 | --tw-numeric-figure: ; 486 | --tw-numeric-spacing: ; 487 | --tw-numeric-fraction: ; 488 | --tw-ring-inset: ; 489 | --tw-ring-offset-width: 0px; 490 | --tw-ring-offset-color: #fff; 491 | --tw-ring-color: rgb(59 130 246 / 0.5); 492 | --tw-ring-offset-shadow: 0 0 #0000; 493 | --tw-ring-shadow: 0 0 #0000; 494 | --tw-shadow: 0 0 #0000; 495 | --tw-shadow-colored: 0 0 #0000; 496 | --tw-blur: ; 497 | --tw-brightness: ; 498 | --tw-contrast: ; 499 | --tw-grayscale: ; 500 | --tw-hue-rotate: ; 501 | --tw-invert: ; 502 | --tw-saturate: ; 503 | --tw-sepia: ; 504 | --tw-drop-shadow: ; 505 | --tw-backdrop-blur: ; 506 | --tw-backdrop-brightness: ; 507 | --tw-backdrop-contrast: ; 508 | --tw-backdrop-grayscale: ; 509 | --tw-backdrop-hue-rotate: ; 510 | --tw-backdrop-invert: ; 511 | --tw-backdrop-opacity: ; 512 | --tw-backdrop-saturate: ; 513 | --tw-backdrop-sepia: ; 514 | } 515 | 516 | .static { 517 | position: static; 518 | } 519 | 520 | .fixed { 521 | position: fixed; 522 | } 523 | 524 | .top-0 { 525 | top: 0px; 526 | } 527 | 528 | .z-10 { 529 | z-index: 10; 530 | } 531 | 532 | .my-14 { 533 | margin-top: 3.5rem; 534 | margin-bottom: 3.5rem; 535 | } 536 | 537 | .mb-4 { 538 | margin-bottom: 1rem; 539 | } 540 | 541 | .mb-6 { 542 | margin-bottom: 1.5rem; 543 | } 544 | 545 | .ml-2 { 546 | margin-left: 0.5rem; 547 | } 548 | 549 | .ml-4 { 550 | margin-left: 1rem; 551 | } 552 | 553 | .ml-auto { 554 | margin-left: auto; 555 | } 556 | 557 | .mr-10 { 558 | margin-right: 2.5rem; 559 | } 560 | 561 | .mr-16 { 562 | margin-right: 4rem; 563 | } 564 | 565 | .mr-2 { 566 | margin-right: 0.5rem; 567 | } 568 | 569 | .mr-4 { 570 | margin-right: 1rem; 571 | } 572 | 573 | .mr-8 { 574 | margin-right: 2rem; 575 | } 576 | 577 | .mr-auto { 578 | margin-right: auto; 579 | } 580 | 581 | .mt-10 { 582 | margin-top: 2.5rem; 583 | } 584 | 585 | .mt-12 { 586 | margin-top: 3rem; 587 | } 588 | 589 | .mt-4 { 590 | margin-top: 1rem; 591 | } 592 | 593 | .mt-40 { 594 | margin-top: 10rem; 595 | } 596 | 597 | .flex { 598 | display: flex; 599 | } 600 | 601 | .h-10 { 602 | height: 2.5rem; 603 | } 604 | 605 | .h-16 { 606 | height: 4rem; 607 | } 608 | 609 | .h-24 { 610 | height: 6rem; 611 | } 612 | 613 | .h-32 { 614 | height: 8rem; 615 | } 616 | 617 | .w-40 { 618 | width: 10rem; 619 | } 620 | 621 | .w-9 { 622 | width: 2.25rem; 623 | } 624 | 625 | .w-full { 626 | width: 100%; 627 | } 628 | 629 | .max-w-3xl { 630 | max-width: 48rem; 631 | } 632 | 633 | .max-w-4xl { 634 | max-width: 56rem; 635 | } 636 | 637 | .grow { 638 | flex-grow: 1; 639 | } 640 | 641 | .flex-col { 642 | flex-direction: column; 643 | } 644 | 645 | .items-center { 646 | align-items: center; 647 | } 648 | 649 | .justify-center { 650 | justify-content: center; 651 | } 652 | 653 | .justify-between { 654 | justify-content: space-between; 655 | } 656 | 657 | .justify-evenly { 658 | justify-content: space-evenly; 659 | } 660 | 661 | .self-start { 662 | align-self: flex-start; 663 | } 664 | 665 | .self-end { 666 | align-self: flex-end; 667 | } 668 | 669 | .whitespace-nowrap { 670 | white-space: nowrap; 671 | } 672 | 673 | .rounded { 674 | border-radius: 0.25rem; 675 | } 676 | 677 | .rounded-lg { 678 | border-radius: 0.5rem; 679 | } 680 | 681 | .rounded-t-lg { 682 | border-top-left-radius: 0.5rem; 683 | border-top-right-radius: 0.5rem; 684 | } 685 | 686 | .border { 687 | border-width: 1px; 688 | } 689 | 690 | .bg-emerald-200 { 691 | --tw-bg-opacity: 1; 692 | background-color: rgb(167 243 208 / var(--tw-bg-opacity)); 693 | } 694 | 695 | .bg-emerald-700 { 696 | --tw-bg-opacity: 1; 697 | background-color: rgb(4 120 87 / var(--tw-bg-opacity)); 698 | } 699 | 700 | .bg-green-700 { 701 | --tw-bg-opacity: 1; 702 | background-color: rgb(21 128 61 / var(--tw-bg-opacity)); 703 | } 704 | 705 | .bg-slate-300 { 706 | --tw-bg-opacity: 1; 707 | background-color: rgb(203 213 225 / var(--tw-bg-opacity)); 708 | } 709 | 710 | .bg-slate-50 { 711 | --tw-bg-opacity: 1; 712 | background-color: rgb(248 250 252 / var(--tw-bg-opacity)); 713 | } 714 | 715 | .p-3 { 716 | padding: 0.75rem; 717 | } 718 | 719 | .px-10 { 720 | padding-left: 2.5rem; 721 | padding-right: 2.5rem; 722 | } 723 | 724 | .px-2 { 725 | padding-left: 0.5rem; 726 | padding-right: 0.5rem; 727 | } 728 | 729 | .px-3 { 730 | padding-left: 0.75rem; 731 | padding-right: 0.75rem; 732 | } 733 | 734 | .py-1 { 735 | padding-top: 0.25rem; 736 | padding-bottom: 0.25rem; 737 | } 738 | 739 | .pb-8 { 740 | padding-bottom: 2rem; 741 | } 742 | 743 | .text-center { 744 | text-align: center; 745 | } 746 | 747 | .font-emoji { 748 | font-family: Material Icons, sans-serif; 749 | } 750 | 751 | .text-2xl { 752 | font-size: 1.5rem; 753 | line-height: 2rem; 754 | } 755 | 756 | .text-4xl { 757 | font-size: 2.25rem; 758 | line-height: 2.5rem; 759 | } 760 | 761 | .text-lg { 762 | font-size: 1.125rem; 763 | line-height: 1.75rem; 764 | } 765 | 766 | .font-bold { 767 | font-weight: 700; 768 | } 769 | 770 | .tracking-wider { 771 | letter-spacing: 0.05em; 772 | } 773 | 774 | .text-emerald-600 { 775 | --tw-text-opacity: 1; 776 | color: rgb(5 150 105 / var(--tw-text-opacity)); 777 | } 778 | 779 | .text-slate-50 { 780 | --tw-text-opacity: 1; 781 | color: rgb(248 250 252 / var(--tw-text-opacity)); 782 | } 783 | 784 | .text-white { 785 | --tw-text-opacity: 1; 786 | color: rgb(255 255 255 / var(--tw-text-opacity)); 787 | } 788 | 789 | .shadow { 790 | --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 791 | --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); 792 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 793 | } 794 | 795 | .shadow-lg { 796 | --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); 797 | --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); 798 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 799 | } 800 | 801 | body { 802 | font-family: Ubuntu, sans-serif; 803 | background-color: #eeeeee; 804 | background-image: url("data:image/svg+xml,%3Csvg width='52' height='26' viewBox='0 0 52 26' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23aeb8af' fill-opacity='0.4'%3E%3Cpath d='M10 10c0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4v2c-3.314 0-6-2.686-6-6 0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6zm25.464-1.95l8.486 8.486-1.414 1.414-8.486-8.486 1.414-1.414z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); 805 | } 806 | 807 | .hover\:bg-green-800:hover { 808 | --tw-bg-opacity: 1; 809 | background-color: rgb(22 101 52 / var(--tw-bg-opacity)); 810 | } 811 | 812 | .hover\:bg-slate-400:hover { 813 | --tw-bg-opacity: 1; 814 | background-color: rgb(148 163 184 / var(--tw-bg-opacity)); 815 | } 816 | -------------------------------------------------------------------------------- /app/distroless-giropops-senhas/static/css/styles.css: -------------------------------------------------------------------------------- 1 | /* styles.css */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | body { 7 | font-family: Ubuntu, sans-serif; 8 | background-color: #eeeeee; 9 | background-image: url("data:image/svg+xml,%3Csvg width='52' height='26' viewBox='0 0 52 26' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23aeb8af' fill-opacity='0.4'%3E%3Cpath d='M10 10c0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4v2c-3.314 0-6-2.686-6-6 0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6zm25.464-1.95l8.486 8.486-1.414 1.414-8.486-8.486 1.414-1.414z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); 10 | } 11 | -------------------------------------------------------------------------------- /app/distroless-giropops-senhas/static/js/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function showSenha() { 4 | const input = document.getElementById("senha"); 5 | const senhaIcon = document.getElementById("senha-icon"); 6 | if (input.attributes.type.nodeValue == "password") { 7 | input.setAttribute("type", "text"); 8 | senhaIcon.innerText = "visibility_off"; 9 | } else { 10 | input.setAttribute("type", "password"); 11 | senhaIcon.innerText = "visibility"; 12 | } 13 | } 14 | function showSenhaPorId(id) { 15 | console.log(id); 16 | const input = document.getElementById("senha-" + id); 17 | const senhaIcon = document.getElementById("senha-icon-" + id); 18 | if (input.attributes.type.nodeValue == "password") { 19 | input.setAttribute("type", "text"); 20 | senhaIcon.innerText = "visibility_off"; 21 | } else { 22 | input.setAttribute("type", "password"); 23 | senhaIcon.innerText = "visibility"; 24 | } 25 | } 26 | function copiarParaAreaDeTransferencia() { 27 | const senhaElemento = document.getElementById("senha"); 28 | navigator.clipboard.writeText(senhaElemento.value).then( 29 | () => { 30 | alert("Senha copiada para a área de transferência!"); 31 | }, 32 | (err) => { 33 | alert("Não foi possível copiar a senha: " + err); 34 | } 35 | ); 36 | } 37 | 38 | function copiarParaAreaDeTransferenciaPorId(id) { 39 | const senhaElemento = document.getElementById("senha-" + id); 40 | navigator.clipboard.writeText(senhaElemento.value).then( 41 | () => { 42 | alert("Senha copiada para a área de transferência!"); 43 | }, 44 | (err) => { 45 | alert("Não foi possível copiar a senha: " + err); 46 | } 47 | ); 48 | } 49 | 50 | function toggleUsuarios() { 51 | const listaUsuariosContainer = document.getElementById( 52 | "lista-usuarios-container" 53 | ); 54 | listaUsuariosContainer.classList.toggle("hidden"); 55 | } 56 | function buscarUltimasSenhas() { 57 | navigation.reload(); 58 | } 59 | -------------------------------------------------------------------------------- /app/distroless-giropops-senhas/static/linuxtips-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/app/distroless-giropops-senhas/static/linuxtips-logo.png -------------------------------------------------------------------------------- /app/distroless-giropops-senhas/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./templates/*.html"], 4 | theme: { 5 | fontFamily: { 6 | emoji: ["Material Icons", "sans-serif"], 7 | }, 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /app/distroless-giropops-senhas/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gerador de Senhas 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 25 |
26 |
27 |
28 |
29 |
30 |

Gerar senha

31 |
32 |
33 |
34 | 35 | 36 | 8 37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 | 45 |
46 | 50 |
51 | {% if senha %} 52 |
53 |

Senha gerada:

54 | 56 |
57 | 61 | 65 |
66 | {% endif %} 67 |
68 |
69 |
70 |
71 |
72 |
73 |

74 | Últimas senhas criadas 75 |

76 |
77 | 82 | {% include 'lista_senhas.html' %} 83 |
84 |
85 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /app/distroless-giropops-senhas/templates/lista_senhas.html: -------------------------------------------------------------------------------- 1 |
2 | 35 |
36 | -------------------------------------------------------------------------------- /app/melange-giropops-senhas/.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "javascript" 5 | 6 | [[analyzers]] 7 | name = "python" 8 | 9 | [analyzers.meta] 10 | runtime_version = "3.x.x" -------------------------------------------------------------------------------- /app/melange-giropops-senhas/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, jsonify 2 | import logging 3 | import os 4 | from prometheus_client import Counter, Histogram, Gauge, start_http_server, generate_latest 5 | import random 6 | import redis 7 | import socket 8 | import string 9 | import time 10 | 11 | app = Flask(__name__, 12 | template_folder='/usr/share/webapps/giropops-senhas/templates', 13 | static_folder='/usr/share/webapps/giropops-senhas/static') 14 | 15 | redis_host = os.environ.get('REDIS_HOST', 'redis-service') 16 | redis_port = 6379 17 | redis_password = "" 18 | 19 | senha_gerada_counter = Counter('senha_gerada', 'Contador de senhas geradas') 20 | senha_gerada_numeros_counter = Counter('senha_gerada_numeros', 'Contador de senhas geradas com números') 21 | senha_gerada_caracteres_especiais_counter = Counter('senha_gerada_caracteres_especiais', 'Contador de senhas geradas com caracteres especiais') 22 | senha_gerada_sem_caracteres_especiais_counter = Counter('senha_gerada_sem_caracteres_especiais', 'Contador de senhas geradas sem caracteres especiais') 23 | senha_gerada_sem_numeros_counter = Counter('senha_gerada_sem_numeros', 'Contador de senhas geradas sem números') 24 | redis_connection_error_counter = Counter('redis_connection_errors', 'Contador de erros de conexão com Redis') 25 | tempo_gerar_senha_histogram = Histogram('tempo_gerar_senha', 'Tempo para gerar uma senha') 26 | tempo_resposta_api_histogram = Histogram('tempo_resposta_api', 'Tempo de resposta da API') 27 | api_erro_counter = Counter('api_errors', 'Contador de erros de API', ['endpoint', 'status_code']) 28 | tamanho_fila_senhas_gauge = Gauge('tamanho_fila_senhas', 'Tamanho da fila de senhas no Redis') 29 | 30 | try: 31 | r = redis.StrictRedis(host=redis_host, port=redis_port, password=redis_password, decode_responses=True) 32 | r.ping() 33 | except redis.ConnectionError: 34 | logging.error("Erro ao conectar ao Redis") 35 | redis_connection_error_counter.inc() 36 | r = None 37 | 38 | def criar_senha(tamanho, incluir_numeros, incluir_caracteres_especiais): 39 | caracteres = string.ascii_letters 40 | 41 | if incluir_numeros: 42 | caracteres += string.digits 43 | senha_gerada_numeros_counter.inc() 44 | 45 | if incluir_caracteres_especiais: 46 | caracteres += string.punctuation 47 | senha_gerada_caracteres_especiais_counter.inc() 48 | else: 49 | senha_gerada_sem_caracteres_especiais_counter.inc() 50 | 51 | senha = ''.join(random.choices(caracteres, k=tamanho)) 52 | 53 | if not any(char.isdigit() for char in senha): 54 | senha_gerada_sem_numeros_counter.inc() 55 | 56 | return senha 57 | 58 | @app.route('/', methods=['GET', 'POST']) 59 | def index(): 60 | if request.method == 'POST': 61 | tamanho = int(request.form.get('tamanho', 8)) 62 | incluir_numeros = request.form.get('incluir_numeros') == 'on' 63 | incluir_caracteres_especiais = request.form.get('incluir_caracteres_especiais') == 'on' 64 | 65 | #Medir time de gerar uma senha: 66 | start_time = time.time() 67 | senha = criar_senha(tamanho, incluir_numeros, incluir_caracteres_especiais) 68 | tempo_gerar_senha_histogram.observe(time.time() - start_time) 69 | 70 | if r: 71 | r.lpush("senhas", senha) 72 | senha_gerada_counter.inc() 73 | 74 | senhas = r.lrange("senhas", 0, 9) if r else [] 75 | if senhas: 76 | senhas_geradas = [{"id": index + 1, "senha": senha} for index, senha in enumerate(senhas)] 77 | return render_template('index.html', senhas_geradas=senhas_geradas, senha=senhas_geradas[0]['senha'] or '' ) 78 | return render_template('index.html') 79 | 80 | 81 | @app.route('/api/gerar-senha', methods=['POST']) 82 | @tempo_resposta_api_histogram.time() 83 | def gerar_senha_api(): 84 | dados = request.get_json() 85 | 86 | tamanho = int(dados.get('tamanho', 8)) 87 | incluir_numeros = dados.get('incluir_numeros', False) 88 | incluir_caracteres_especiais = dados.get('incluir_caracteres_especiais', False) 89 | 90 | start_time = time.time() 91 | try: 92 | senha = criar_senha(tamanho, incluir_numeros, incluir_caracteres_especiais) 93 | tempo_gerar_senha_histogram.observe(time.time() - start_time) 94 | if r: 95 | r.lpush("senhas", senha) 96 | senha_gerada_counter.inc() 97 | return jsonify({"senha": senha}), 200 98 | except Exception as e: 99 | api_erro_counter.labels(endpoint='/api/gerar-senha', status_code='500').inc() 100 | logging.error(f"Erro na API /api/gerar-senha: {str(e)}", exc_info=True) 101 | return jsonify({"error": "Erro ao gerar senha"}), 500 102 | 103 | @app.route('/api/senhas', methods=['GET']) 104 | @tempo_resposta_api_histogram.time() 105 | def listar_senhas(): 106 | try: 107 | senhas = r.lrange("senhas", 0, 9) if r else [] 108 | tamanho_fila_senhas_gauge.set(len(senhas)) 109 | 110 | resposta = [{"id": index + 1, "senha": senha} for index, senha in enumerate(senhas)] 111 | return jsonify(resposta) 112 | except Exception as e: 113 | api_erro_counter.labels(endpoint='/api/senhas', status_code='500').inc() 114 | logging.error(f"Erro na API /api/senhas: {str(e)}", exc_info=True) 115 | return jsonify({"error": "Erro ao listar senhas"}), 500 116 | 117 | @app.route('/metrics') 118 | def metrics(): 119 | return generate_latest() 120 | 121 | def start_prometheus_server(port): 122 | try: 123 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 124 | s.settimeout(1) 125 | if s.connect_ex(('0.0.0.0', port)) == 0: 126 | logging.error(f"Porta {port} já está em uso. Não será possível iniciar Prometheus.") 127 | return False 128 | 129 | start_http_server(port) 130 | return True 131 | except Exception as e: 132 | logging.error(f"Erro ao tentar iniciar Prometheus: {str(e)}", exc_info=True) 133 | return False 134 | 135 | if __name__ == '__main__': 136 | logging.basicConfig(filename='tmp/error.log', level=logging.DEBUG) 137 | port = 8089 138 | app.run(host='0.0.0.0', debug=False) 139 | -------------------------------------------------------------------------------- /app/melange-giropops-senhas/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==3.0.3 2 | redis==4.5.4 3 | prometheus-client==0.16.0 -------------------------------------------------------------------------------- /app/melange-giropops-senhas/static/css/output.css: -------------------------------------------------------------------------------- 1 | /* styles.css */ 2 | 3 | /* ! tailwindcss v3.2.7 | MIT License | https://tailwindcss.com */ 4 | 5 | /* 6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) 7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) 8 | */ 9 | 10 | *, 11 | ::before, 12 | ::after { 13 | box-sizing: border-box; 14 | /* 1 */ 15 | border-width: 0; 16 | /* 2 */ 17 | border-style: solid; 18 | /* 2 */ 19 | border-color: #e5e7eb; 20 | /* 2 */ 21 | } 22 | 23 | ::before, 24 | ::after { 25 | --tw-content: ''; 26 | } 27 | 28 | /* 29 | 1. Use a consistent sensible line-height in all browsers. 30 | 2. Prevent adjustments of font size after orientation changes in iOS. 31 | 3. Use a more readable tab size. 32 | 4. Use the user's configured `sans` font-family by default. 33 | 5. Use the user's configured `sans` font-feature-settings by default. 34 | */ 35 | 36 | html { 37 | line-height: 1.5; 38 | /* 1 */ 39 | -webkit-text-size-adjust: 100%; 40 | /* 2 */ 41 | -moz-tab-size: 4; 42 | /* 3 */ 43 | -o-tab-size: 4; 44 | tab-size: 4; 45 | /* 3 */ 46 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 47 | /* 4 */ 48 | font-feature-settings: normal; 49 | /* 5 */ 50 | } 51 | 52 | /* 53 | 1. Remove the margin in all browsers. 54 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. 55 | */ 56 | 57 | body { 58 | margin: 0; 59 | /* 1 */ 60 | line-height: inherit; 61 | /* 2 */ 62 | } 63 | 64 | /* 65 | 1. Add the correct height in Firefox. 66 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) 67 | 3. Ensure horizontal rules are visible by default. 68 | */ 69 | 70 | hr { 71 | height: 0; 72 | /* 1 */ 73 | color: inherit; 74 | /* 2 */ 75 | border-top-width: 1px; 76 | /* 3 */ 77 | } 78 | 79 | /* 80 | Add the correct text decoration in Chrome, Edge, and Safari. 81 | */ 82 | 83 | abbr:where([title]) { 84 | -webkit-text-decoration: underline dotted; 85 | text-decoration: underline dotted; 86 | } 87 | 88 | /* 89 | Remove the default font size and weight for headings. 90 | */ 91 | 92 | h1, 93 | h2, 94 | h3, 95 | h4, 96 | h5, 97 | h6 { 98 | font-size: inherit; 99 | font-weight: inherit; 100 | } 101 | 102 | /* 103 | Reset links to optimize for opt-in styling instead of opt-out. 104 | */ 105 | 106 | a { 107 | color: inherit; 108 | text-decoration: inherit; 109 | } 110 | 111 | /* 112 | Add the correct font weight in Edge and Safari. 113 | */ 114 | 115 | b, 116 | strong { 117 | font-weight: bolder; 118 | } 119 | 120 | /* 121 | 1. Use the user's configured `mono` font family by default. 122 | 2. Correct the odd `em` font sizing in all browsers. 123 | */ 124 | 125 | code, 126 | kbd, 127 | samp, 128 | pre { 129 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 130 | /* 1 */ 131 | font-size: 1em; 132 | /* 2 */ 133 | } 134 | 135 | /* 136 | Add the correct font size in all browsers. 137 | */ 138 | 139 | small { 140 | font-size: 80%; 141 | } 142 | 143 | /* 144 | Prevent `sub` and `sup` elements from affecting the line height in all browsers. 145 | */ 146 | 147 | sub, 148 | sup { 149 | font-size: 75%; 150 | line-height: 0; 151 | position: relative; 152 | vertical-align: baseline; 153 | } 154 | 155 | sub { 156 | bottom: -0.25em; 157 | } 158 | 159 | sup { 160 | top: -0.5em; 161 | } 162 | 163 | /* 164 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) 165 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) 166 | 3. Remove gaps between table borders by default. 167 | */ 168 | 169 | table { 170 | text-indent: 0; 171 | /* 1 */ 172 | border-color: inherit; 173 | /* 2 */ 174 | border-collapse: collapse; 175 | /* 3 */ 176 | } 177 | 178 | /* 179 | 1. Change the font styles in all browsers. 180 | 2. Remove the margin in Firefox and Safari. 181 | 3. Remove default padding in all browsers. 182 | */ 183 | 184 | button, 185 | input, 186 | optgroup, 187 | select, 188 | textarea { 189 | font-family: inherit; 190 | /* 1 */ 191 | font-size: 100%; 192 | /* 1 */ 193 | font-weight: inherit; 194 | /* 1 */ 195 | line-height: inherit; 196 | /* 1 */ 197 | color: inherit; 198 | /* 1 */ 199 | margin: 0; 200 | /* 2 */ 201 | padding: 0; 202 | /* 3 */ 203 | } 204 | 205 | /* 206 | Remove the inheritance of text transform in Edge and Firefox. 207 | */ 208 | 209 | button, 210 | select { 211 | text-transform: none; 212 | } 213 | 214 | /* 215 | 1. Correct the inability to style clickable types in iOS and Safari. 216 | 2. Remove default button styles. 217 | */ 218 | 219 | button, 220 | [type='button'], 221 | [type='reset'], 222 | [type='submit'] { 223 | -webkit-appearance: button; 224 | /* 1 */ 225 | background-color: transparent; 226 | /* 2 */ 227 | background-image: none; 228 | /* 2 */ 229 | } 230 | 231 | /* 232 | Use the modern Firefox focus style for all focusable elements. 233 | */ 234 | 235 | :-moz-focusring { 236 | outline: auto; 237 | } 238 | 239 | /* 240 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) 241 | */ 242 | 243 | :-moz-ui-invalid { 244 | box-shadow: none; 245 | } 246 | 247 | /* 248 | Add the correct vertical alignment in Chrome and Firefox. 249 | */ 250 | 251 | progress { 252 | vertical-align: baseline; 253 | } 254 | 255 | /* 256 | Correct the cursor style of increment and decrement buttons in Safari. 257 | */ 258 | 259 | ::-webkit-inner-spin-button, 260 | ::-webkit-outer-spin-button { 261 | height: auto; 262 | } 263 | 264 | /* 265 | 1. Correct the odd appearance in Chrome and Safari. 266 | 2. Correct the outline style in Safari. 267 | */ 268 | 269 | [type='search'] { 270 | -webkit-appearance: textfield; 271 | /* 1 */ 272 | outline-offset: -2px; 273 | /* 2 */ 274 | } 275 | 276 | /* 277 | Remove the inner padding in Chrome and Safari on macOS. 278 | */ 279 | 280 | ::-webkit-search-decoration { 281 | -webkit-appearance: none; 282 | } 283 | 284 | /* 285 | 1. Correct the inability to style clickable types in iOS and Safari. 286 | 2. Change font properties to `inherit` in Safari. 287 | */ 288 | 289 | ::-webkit-file-upload-button { 290 | -webkit-appearance: button; 291 | /* 1 */ 292 | font: inherit; 293 | /* 2 */ 294 | } 295 | 296 | /* 297 | Add the correct display in Chrome and Safari. 298 | */ 299 | 300 | summary { 301 | display: list-item; 302 | } 303 | 304 | /* 305 | Removes the default spacing and border for appropriate elements. 306 | */ 307 | 308 | blockquote, 309 | dl, 310 | dd, 311 | h1, 312 | h2, 313 | h3, 314 | h4, 315 | h5, 316 | h6, 317 | hr, 318 | figure, 319 | p, 320 | pre { 321 | margin: 0; 322 | } 323 | 324 | fieldset { 325 | margin: 0; 326 | padding: 0; 327 | } 328 | 329 | legend { 330 | padding: 0; 331 | } 332 | 333 | ol, 334 | ul, 335 | menu { 336 | list-style: none; 337 | margin: 0; 338 | padding: 0; 339 | } 340 | 341 | /* 342 | Prevent resizing textareas horizontally by default. 343 | */ 344 | 345 | textarea { 346 | resize: vertical; 347 | } 348 | 349 | /* 350 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) 351 | 2. Set the default placeholder color to the user's configured gray 400 color. 352 | */ 353 | 354 | input::-moz-placeholder, textarea::-moz-placeholder { 355 | opacity: 1; 356 | /* 1 */ 357 | color: #9ca3af; 358 | /* 2 */ 359 | } 360 | 361 | input::placeholder, 362 | textarea::placeholder { 363 | opacity: 1; 364 | /* 1 */ 365 | color: #9ca3af; 366 | /* 2 */ 367 | } 368 | 369 | /* 370 | Set the default cursor for buttons. 371 | */ 372 | 373 | button, 374 | [role="button"] { 375 | cursor: pointer; 376 | } 377 | 378 | /* 379 | Make sure disabled buttons don't get the pointer cursor. 380 | */ 381 | 382 | :disabled { 383 | cursor: default; 384 | } 385 | 386 | /* 387 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) 388 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) 389 | This can trigger a poorly considered lint error in some tools but is included by design. 390 | */ 391 | 392 | img, 393 | svg, 394 | video, 395 | canvas, 396 | audio, 397 | iframe, 398 | embed, 399 | object { 400 | display: block; 401 | /* 1 */ 402 | vertical-align: middle; 403 | /* 2 */ 404 | } 405 | 406 | /* 407 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) 408 | */ 409 | 410 | img, 411 | video { 412 | max-width: 100%; 413 | height: auto; 414 | } 415 | 416 | /* Make elements with the HTML hidden attribute stay hidden by default */ 417 | 418 | [hidden] { 419 | display: none; 420 | } 421 | 422 | *, ::before, ::after { 423 | --tw-border-spacing-x: 0; 424 | --tw-border-spacing-y: 0; 425 | --tw-translate-x: 0; 426 | --tw-translate-y: 0; 427 | --tw-rotate: 0; 428 | --tw-skew-x: 0; 429 | --tw-skew-y: 0; 430 | --tw-scale-x: 1; 431 | --tw-scale-y: 1; 432 | --tw-pan-x: ; 433 | --tw-pan-y: ; 434 | --tw-pinch-zoom: ; 435 | --tw-scroll-snap-strictness: proximity; 436 | --tw-ordinal: ; 437 | --tw-slashed-zero: ; 438 | --tw-numeric-figure: ; 439 | --tw-numeric-spacing: ; 440 | --tw-numeric-fraction: ; 441 | --tw-ring-inset: ; 442 | --tw-ring-offset-width: 0px; 443 | --tw-ring-offset-color: #fff; 444 | --tw-ring-color: rgb(59 130 246 / 0.5); 445 | --tw-ring-offset-shadow: 0 0 #0000; 446 | --tw-ring-shadow: 0 0 #0000; 447 | --tw-shadow: 0 0 #0000; 448 | --tw-shadow-colored: 0 0 #0000; 449 | --tw-blur: ; 450 | --tw-brightness: ; 451 | --tw-contrast: ; 452 | --tw-grayscale: ; 453 | --tw-hue-rotate: ; 454 | --tw-invert: ; 455 | --tw-saturate: ; 456 | --tw-sepia: ; 457 | --tw-drop-shadow: ; 458 | --tw-backdrop-blur: ; 459 | --tw-backdrop-brightness: ; 460 | --tw-backdrop-contrast: ; 461 | --tw-backdrop-grayscale: ; 462 | --tw-backdrop-hue-rotate: ; 463 | --tw-backdrop-invert: ; 464 | --tw-backdrop-opacity: ; 465 | --tw-backdrop-saturate: ; 466 | --tw-backdrop-sepia: ; 467 | } 468 | 469 | ::backdrop { 470 | --tw-border-spacing-x: 0; 471 | --tw-border-spacing-y: 0; 472 | --tw-translate-x: 0; 473 | --tw-translate-y: 0; 474 | --tw-rotate: 0; 475 | --tw-skew-x: 0; 476 | --tw-skew-y: 0; 477 | --tw-scale-x: 1; 478 | --tw-scale-y: 1; 479 | --tw-pan-x: ; 480 | --tw-pan-y: ; 481 | --tw-pinch-zoom: ; 482 | --tw-scroll-snap-strictness: proximity; 483 | --tw-ordinal: ; 484 | --tw-slashed-zero: ; 485 | --tw-numeric-figure: ; 486 | --tw-numeric-spacing: ; 487 | --tw-numeric-fraction: ; 488 | --tw-ring-inset: ; 489 | --tw-ring-offset-width: 0px; 490 | --tw-ring-offset-color: #fff; 491 | --tw-ring-color: rgb(59 130 246 / 0.5); 492 | --tw-ring-offset-shadow: 0 0 #0000; 493 | --tw-ring-shadow: 0 0 #0000; 494 | --tw-shadow: 0 0 #0000; 495 | --tw-shadow-colored: 0 0 #0000; 496 | --tw-blur: ; 497 | --tw-brightness: ; 498 | --tw-contrast: ; 499 | --tw-grayscale: ; 500 | --tw-hue-rotate: ; 501 | --tw-invert: ; 502 | --tw-saturate: ; 503 | --tw-sepia: ; 504 | --tw-drop-shadow: ; 505 | --tw-backdrop-blur: ; 506 | --tw-backdrop-brightness: ; 507 | --tw-backdrop-contrast: ; 508 | --tw-backdrop-grayscale: ; 509 | --tw-backdrop-hue-rotate: ; 510 | --tw-backdrop-invert: ; 511 | --tw-backdrop-opacity: ; 512 | --tw-backdrop-saturate: ; 513 | --tw-backdrop-sepia: ; 514 | } 515 | 516 | .static { 517 | position: static; 518 | } 519 | 520 | .fixed { 521 | position: fixed; 522 | } 523 | 524 | .top-0 { 525 | top: 0px; 526 | } 527 | 528 | .z-10 { 529 | z-index: 10; 530 | } 531 | 532 | .my-14 { 533 | margin-top: 3.5rem; 534 | margin-bottom: 3.5rem; 535 | } 536 | 537 | .mb-4 { 538 | margin-bottom: 1rem; 539 | } 540 | 541 | .mb-6 { 542 | margin-bottom: 1.5rem; 543 | } 544 | 545 | .ml-2 { 546 | margin-left: 0.5rem; 547 | } 548 | 549 | .ml-4 { 550 | margin-left: 1rem; 551 | } 552 | 553 | .ml-auto { 554 | margin-left: auto; 555 | } 556 | 557 | .mr-10 { 558 | margin-right: 2.5rem; 559 | } 560 | 561 | .mr-16 { 562 | margin-right: 4rem; 563 | } 564 | 565 | .mr-2 { 566 | margin-right: 0.5rem; 567 | } 568 | 569 | .mr-4 { 570 | margin-right: 1rem; 571 | } 572 | 573 | .mr-8 { 574 | margin-right: 2rem; 575 | } 576 | 577 | .mr-auto { 578 | margin-right: auto; 579 | } 580 | 581 | .mt-10 { 582 | margin-top: 2.5rem; 583 | } 584 | 585 | .mt-12 { 586 | margin-top: 3rem; 587 | } 588 | 589 | .mt-4 { 590 | margin-top: 1rem; 591 | } 592 | 593 | .mt-40 { 594 | margin-top: 10rem; 595 | } 596 | 597 | .flex { 598 | display: flex; 599 | } 600 | 601 | .h-10 { 602 | height: 2.5rem; 603 | } 604 | 605 | .h-16 { 606 | height: 4rem; 607 | } 608 | 609 | .h-24 { 610 | height: 6rem; 611 | } 612 | 613 | .h-32 { 614 | height: 8rem; 615 | } 616 | 617 | .w-40 { 618 | width: 10rem; 619 | } 620 | 621 | .w-9 { 622 | width: 2.25rem; 623 | } 624 | 625 | .w-full { 626 | width: 100%; 627 | } 628 | 629 | .max-w-3xl { 630 | max-width: 48rem; 631 | } 632 | 633 | .max-w-4xl { 634 | max-width: 56rem; 635 | } 636 | 637 | .grow { 638 | flex-grow: 1; 639 | } 640 | 641 | .flex-col { 642 | flex-direction: column; 643 | } 644 | 645 | .items-center { 646 | align-items: center; 647 | } 648 | 649 | .justify-center { 650 | justify-content: center; 651 | } 652 | 653 | .justify-between { 654 | justify-content: space-between; 655 | } 656 | 657 | .justify-evenly { 658 | justify-content: space-evenly; 659 | } 660 | 661 | .self-start { 662 | align-self: flex-start; 663 | } 664 | 665 | .self-end { 666 | align-self: flex-end; 667 | } 668 | 669 | .whitespace-nowrap { 670 | white-space: nowrap; 671 | } 672 | 673 | .rounded { 674 | border-radius: 0.25rem; 675 | } 676 | 677 | .rounded-lg { 678 | border-radius: 0.5rem; 679 | } 680 | 681 | .rounded-t-lg { 682 | border-top-left-radius: 0.5rem; 683 | border-top-right-radius: 0.5rem; 684 | } 685 | 686 | .border { 687 | border-width: 1px; 688 | } 689 | 690 | .bg-emerald-200 { 691 | --tw-bg-opacity: 1; 692 | background-color: rgb(167 243 208 / var(--tw-bg-opacity)); 693 | } 694 | 695 | .bg-emerald-700 { 696 | --tw-bg-opacity: 1; 697 | background-color: rgb(4 120 87 / var(--tw-bg-opacity)); 698 | } 699 | 700 | .bg-green-700 { 701 | --tw-bg-opacity: 1; 702 | background-color: rgb(21 128 61 / var(--tw-bg-opacity)); 703 | } 704 | 705 | .bg-slate-300 { 706 | --tw-bg-opacity: 1; 707 | background-color: rgb(203 213 225 / var(--tw-bg-opacity)); 708 | } 709 | 710 | .bg-slate-50 { 711 | --tw-bg-opacity: 1; 712 | background-color: rgb(248 250 252 / var(--tw-bg-opacity)); 713 | } 714 | 715 | .p-3 { 716 | padding: 0.75rem; 717 | } 718 | 719 | .px-10 { 720 | padding-left: 2.5rem; 721 | padding-right: 2.5rem; 722 | } 723 | 724 | .px-2 { 725 | padding-left: 0.5rem; 726 | padding-right: 0.5rem; 727 | } 728 | 729 | .px-3 { 730 | padding-left: 0.75rem; 731 | padding-right: 0.75rem; 732 | } 733 | 734 | .py-1 { 735 | padding-top: 0.25rem; 736 | padding-bottom: 0.25rem; 737 | } 738 | 739 | .pb-8 { 740 | padding-bottom: 2rem; 741 | } 742 | 743 | .text-center { 744 | text-align: center; 745 | } 746 | 747 | .font-emoji { 748 | font-family: Material Icons, sans-serif; 749 | } 750 | 751 | .text-2xl { 752 | font-size: 1.5rem; 753 | line-height: 2rem; 754 | } 755 | 756 | .text-4xl { 757 | font-size: 2.25rem; 758 | line-height: 2.5rem; 759 | } 760 | 761 | .text-lg { 762 | font-size: 1.125rem; 763 | line-height: 1.75rem; 764 | } 765 | 766 | .font-bold { 767 | font-weight: 700; 768 | } 769 | 770 | .tracking-wider { 771 | letter-spacing: 0.05em; 772 | } 773 | 774 | .text-emerald-600 { 775 | --tw-text-opacity: 1; 776 | color: rgb(5 150 105 / var(--tw-text-opacity)); 777 | } 778 | 779 | .text-slate-50 { 780 | --tw-text-opacity: 1; 781 | color: rgb(248 250 252 / var(--tw-text-opacity)); 782 | } 783 | 784 | .text-white { 785 | --tw-text-opacity: 1; 786 | color: rgb(255 255 255 / var(--tw-text-opacity)); 787 | } 788 | 789 | .shadow { 790 | --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 791 | --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); 792 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 793 | } 794 | 795 | .shadow-lg { 796 | --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); 797 | --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); 798 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 799 | } 800 | 801 | body { 802 | font-family: Ubuntu, sans-serif; 803 | background-color: #eeeeee; 804 | background-image: url("data:image/svg+xml,%3Csvg width='52' height='26' viewBox='0 0 52 26' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23aeb8af' fill-opacity='0.4'%3E%3Cpath d='M10 10c0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4v2c-3.314 0-6-2.686-6-6 0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6zm25.464-1.95l8.486 8.486-1.414 1.414-8.486-8.486 1.414-1.414z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); 805 | } 806 | 807 | .hover\:bg-green-800:hover { 808 | --tw-bg-opacity: 1; 809 | background-color: rgb(22 101 52 / var(--tw-bg-opacity)); 810 | } 811 | 812 | .hover\:bg-slate-400:hover { 813 | --tw-bg-opacity: 1; 814 | background-color: rgb(148 163 184 / var(--tw-bg-opacity)); 815 | } 816 | -------------------------------------------------------------------------------- /app/melange-giropops-senhas/static/css/styles.css: -------------------------------------------------------------------------------- 1 | /* styles.css */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | body { 7 | font-family: Ubuntu, sans-serif; 8 | background-color: #eeeeee; 9 | background-image: url("data:image/svg+xml,%3Csvg width='52' height='26' viewBox='0 0 52 26' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23aeb8af' fill-opacity='0.4'%3E%3Cpath d='M10 10c0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4v2c-3.314 0-6-2.686-6-6 0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6zm25.464-1.95l8.486 8.486-1.414 1.414-8.486-8.486 1.414-1.414z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); 10 | } 11 | -------------------------------------------------------------------------------- /app/melange-giropops-senhas/static/js/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function showSenha() { 4 | const input = document.getElementById("senha"); 5 | const senhaIcon = document.getElementById("senha-icon"); 6 | if (input.attributes.type.nodeValue == "password") { 7 | input.setAttribute("type", "text"); 8 | senhaIcon.innerText = "visibility_off"; 9 | } else { 10 | input.setAttribute("type", "password"); 11 | senhaIcon.innerText = "visibility"; 12 | } 13 | } 14 | function showSenhaPorId(id) { 15 | console.log(id); 16 | const input = document.getElementById("senha-" + id); 17 | const senhaIcon = document.getElementById("senha-icon-" + id); 18 | if (input.attributes.type.nodeValue == "password") { 19 | input.setAttribute("type", "text"); 20 | senhaIcon.innerText = "visibility_off"; 21 | } else { 22 | input.setAttribute("type", "password"); 23 | senhaIcon.innerText = "visibility"; 24 | } 25 | } 26 | function copiarParaAreaDeTransferencia() { 27 | const senhaElemento = document.getElementById("senha"); 28 | navigator.clipboard.writeText(senhaElemento.value).then( 29 | () => { 30 | alert("Senha copiada para a área de transferência!"); 31 | }, 32 | (err) => { 33 | alert("Não foi possível copiar a senha: " + err); 34 | } 35 | ); 36 | } 37 | 38 | function copiarParaAreaDeTransferenciaPorId(id) { 39 | const senhaElemento = document.getElementById("senha-" + id); 40 | navigator.clipboard.writeText(senhaElemento.value).then( 41 | () => { 42 | alert("Senha copiada para a área de transferência!"); 43 | }, 44 | (err) => { 45 | alert("Não foi possível copiar a senha: " + err); 46 | } 47 | ); 48 | } 49 | 50 | function toggleUsuarios() { 51 | const listaUsuariosContainer = document.getElementById( 52 | "lista-usuarios-container" 53 | ); 54 | listaUsuariosContainer.classList.toggle("hidden"); 55 | } 56 | function buscarUltimasSenhas() { 57 | navigation.reload(); 58 | } 59 | -------------------------------------------------------------------------------- /app/melange-giropops-senhas/static/linuxtips-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/app/melange-giropops-senhas/static/linuxtips-logo.png -------------------------------------------------------------------------------- /app/melange-giropops-senhas/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./templates/*.html"], 4 | theme: { 5 | fontFamily: { 6 | emoji: ["Material Icons", "sans-serif"], 7 | }, 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /app/melange-giropops-senhas/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gerador de Senhas 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 25 |
26 |
27 |
28 |
29 |
30 |

Gerar senha

31 |
32 |
33 |
34 | 35 | 36 | 8 37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 | 45 |
46 | 50 |
51 | {% if senha %} 52 |
53 |

Senha gerada:

54 | 56 |
57 | 61 | 65 |
66 | {% endif %} 67 |
68 |
69 |
70 |
71 |
72 |
73 |

74 | Últimas senhas criadas 75 |

76 |
77 | 82 | {% include 'lista_senhas.html' %} 83 |
84 |
85 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /app/melange-giropops-senhas/templates/lista_senhas.html: -------------------------------------------------------------------------------- 1 |
2 | 35 |
36 | -------------------------------------------------------------------------------- /app/normal-giropops-senhas/.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "javascript" 5 | 6 | [[analyzers]] 7 | name = "python" 8 | 9 | [analyzers.meta] 10 | runtime_version = "3.x.x" -------------------------------------------------------------------------------- /app/normal-giropops-senhas/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt . 6 | COPY app.py . 7 | COPY templates templates/ 8 | COPY static static/ 9 | 10 | RUN pip install --no-cache-dir -r requirements.txt 11 | 12 | EXPOSE 5000 13 | 14 | CMD ["flask", "run", "--host=0.0.0.0"] -------------------------------------------------------------------------------- /app/normal-giropops-senhas/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, jsonify 2 | import logging 3 | import os 4 | from prometheus_client import Counter, Histogram, Gauge, start_http_server, generate_latest 5 | import random 6 | import redis 7 | import socket 8 | import string 9 | import time 10 | 11 | app = Flask(__name__, 12 | template_folder='./templates', 13 | static_folder='./static') 14 | 15 | redis_host = os.environ.get('REDIS_HOST', 'redis-service') 16 | redis_port = 6379 17 | redis_password = "" 18 | 19 | senha_gerada_counter = Counter('senha_gerada', 'Contador de senhas geradas') 20 | senha_gerada_numeros_counter = Counter('senha_gerada_numeros', 'Contador de senhas geradas com números') 21 | senha_gerada_caracteres_especiais_counter = Counter('senha_gerada_caracteres_especiais', 'Contador de senhas geradas com caracteres especiais') 22 | senha_gerada_sem_caracteres_especiais_counter = Counter('senha_gerada_sem_caracteres_especiais', 'Contador de senhas geradas sem caracteres especiais') 23 | senha_gerada_sem_numeros_counter = Counter('senha_gerada_sem_numeros', 'Contador de senhas geradas sem números') 24 | redis_connection_error_counter = Counter('redis_connection_errors', 'Contador de erros de conexão com Redis') 25 | tempo_gerar_senha_histogram = Histogram('tempo_gerar_senha', 'Tempo para gerar uma senha') 26 | tempo_resposta_api_histogram = Histogram('tempo_resposta_api', 'Tempo de resposta da API') 27 | api_erro_counter = Counter('api_errors', 'Contador de erros de API', ['endpoint', 'status_code']) 28 | tamanho_fila_senhas_gauge = Gauge('tamanho_fila_senhas', 'Tamanho da fila de senhas no Redis') 29 | 30 | try: 31 | r = redis.StrictRedis(host=redis_host, port=redis_port, password=redis_password, decode_responses=True) 32 | r.ping() 33 | except redis.ConnectionError: 34 | logging.error("Erro ao conectar ao Redis") 35 | redis_connection_error_counter.inc() 36 | r = None 37 | 38 | def criar_senha(tamanho, incluir_numeros, incluir_caracteres_especiais): 39 | caracteres = string.ascii_letters 40 | 41 | if incluir_numeros: 42 | caracteres += string.digits 43 | senha_gerada_numeros_counter.inc() 44 | 45 | if incluir_caracteres_especiais: 46 | caracteres += string.punctuation 47 | senha_gerada_caracteres_especiais_counter.inc() 48 | else: 49 | senha_gerada_sem_caracteres_especiais_counter.inc() 50 | 51 | senha = ''.join(random.choices(caracteres, k=tamanho)) 52 | 53 | if not any(char.isdigit() for char in senha): 54 | senha_gerada_sem_numeros_counter.inc() 55 | 56 | return senha 57 | 58 | @app.route('/', methods=['GET', 'POST']) 59 | def index(): 60 | if request.method == 'POST': 61 | tamanho = int(request.form.get('tamanho', 8)) 62 | incluir_numeros = request.form.get('incluir_numeros') == 'on' 63 | incluir_caracteres_especiais = request.form.get('incluir_caracteres_especiais') == 'on' 64 | 65 | #Medir time de gerar uma senha: 66 | start_time = time.time() 67 | senha = criar_senha(tamanho, incluir_numeros, incluir_caracteres_especiais) 68 | tempo_gerar_senha_histogram.observe(time.time() - start_time) 69 | 70 | if r: 71 | r.lpush("senhas", senha) 72 | senha_gerada_counter.inc() 73 | 74 | senhas = r.lrange("senhas", 0, 9) if r else [] 75 | if senhas: 76 | senhas_geradas = [{"id": index + 1, "senha": senha} for index, senha in enumerate(senhas)] 77 | return render_template('index.html', senhas_geradas=senhas_geradas, senha=senhas_geradas[0]['senha'] or '' ) 78 | return render_template('index.html') 79 | 80 | 81 | @app.route('/api/gerar-senha', methods=['POST']) 82 | @tempo_resposta_api_histogram.time() 83 | def gerar_senha_api(): 84 | dados = request.get_json() 85 | 86 | tamanho = int(dados.get('tamanho', 8)) 87 | incluir_numeros = dados.get('incluir_numeros', False) 88 | incluir_caracteres_especiais = dados.get('incluir_caracteres_especiais', False) 89 | 90 | start_time = time.time() 91 | try: 92 | senha = criar_senha(tamanho, incluir_numeros, incluir_caracteres_especiais) 93 | tempo_gerar_senha_histogram.observe(time.time() - start_time) 94 | if r: 95 | r.lpush("senhas", senha) 96 | senha_gerada_counter.inc() 97 | return jsonify({"senha": senha}), 200 98 | except Exception as e: 99 | api_erro_counter.labels(endpoint='/api/gerar-senha', status_code='500').inc() 100 | logging.error(f"Erro na API /api/gerar-senha: {str(e)}", exc_info=True) 101 | return jsonify({"error": "Erro ao gerar senha"}), 500 102 | 103 | @app.route('/api/senhas', methods=['GET']) 104 | @tempo_resposta_api_histogram.time() 105 | def listar_senhas(): 106 | try: 107 | senhas = r.lrange("senhas", 0, 9) if r else [] 108 | tamanho_fila_senhas_gauge.set(len(senhas)) 109 | 110 | resposta = [{"id": index + 1, "senha": senha} for index, senha in enumerate(senhas)] 111 | return jsonify(resposta) 112 | except Exception as e: 113 | api_erro_counter.labels(endpoint='/api/senhas', status_code='500').inc() 114 | logging.error(f"Erro na API /api/senhas: {str(e)}", exc_info=True) 115 | return jsonify({"error": "Erro ao listar senhas"}), 500 116 | 117 | @app.route('/metrics') 118 | def metrics(): 119 | return generate_latest() 120 | 121 | def start_prometheus_server(port): 122 | try: 123 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 124 | s.settimeout(1) 125 | if s.connect_ex(('0.0.0.0', port)) == 0: 126 | logging.error(f"Porta {port} já está em uso. Não será possível iniciar Prometheus.") 127 | return False 128 | 129 | start_http_server(port) 130 | return True 131 | except Exception as e: 132 | logging.error(f"Erro ao tentar iniciar Prometheus: {str(e)}", exc_info=True) 133 | return False 134 | 135 | if __name__ == '__main__': 136 | logging.basicConfig(filename='tmp/error.log', level=logging.DEBUG) 137 | port = 8089 138 | app.run(host='0.0.0.0', debug=False) 139 | -------------------------------------------------------------------------------- /app/normal-giropops-senhas/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==3.0.3 2 | redis==4.5.4 3 | prometheus-client==0.16.0 -------------------------------------------------------------------------------- /app/normal-giropops-senhas/static/css/output.css: -------------------------------------------------------------------------------- 1 | /* styles.css */ 2 | 3 | /* ! tailwindcss v3.2.7 | MIT License | https://tailwindcss.com */ 4 | 5 | /* 6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) 7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) 8 | */ 9 | 10 | *, 11 | ::before, 12 | ::after { 13 | box-sizing: border-box; 14 | /* 1 */ 15 | border-width: 0; 16 | /* 2 */ 17 | border-style: solid; 18 | /* 2 */ 19 | border-color: #e5e7eb; 20 | /* 2 */ 21 | } 22 | 23 | ::before, 24 | ::after { 25 | --tw-content: ''; 26 | } 27 | 28 | /* 29 | 1. Use a consistent sensible line-height in all browsers. 30 | 2. Prevent adjustments of font size after orientation changes in iOS. 31 | 3. Use a more readable tab size. 32 | 4. Use the user's configured `sans` font-family by default. 33 | 5. Use the user's configured `sans` font-feature-settings by default. 34 | */ 35 | 36 | html { 37 | line-height: 1.5; 38 | /* 1 */ 39 | -webkit-text-size-adjust: 100%; 40 | /* 2 */ 41 | -moz-tab-size: 4; 42 | /* 3 */ 43 | -o-tab-size: 4; 44 | tab-size: 4; 45 | /* 3 */ 46 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 47 | /* 4 */ 48 | font-feature-settings: normal; 49 | /* 5 */ 50 | } 51 | 52 | /* 53 | 1. Remove the margin in all browsers. 54 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. 55 | */ 56 | 57 | body { 58 | margin: 0; 59 | /* 1 */ 60 | line-height: inherit; 61 | /* 2 */ 62 | } 63 | 64 | /* 65 | 1. Add the correct height in Firefox. 66 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) 67 | 3. Ensure horizontal rules are visible by default. 68 | */ 69 | 70 | hr { 71 | height: 0; 72 | /* 1 */ 73 | color: inherit; 74 | /* 2 */ 75 | border-top-width: 1px; 76 | /* 3 */ 77 | } 78 | 79 | /* 80 | Add the correct text decoration in Chrome, Edge, and Safari. 81 | */ 82 | 83 | abbr:where([title]) { 84 | -webkit-text-decoration: underline dotted; 85 | text-decoration: underline dotted; 86 | } 87 | 88 | /* 89 | Remove the default font size and weight for headings. 90 | */ 91 | 92 | h1, 93 | h2, 94 | h3, 95 | h4, 96 | h5, 97 | h6 { 98 | font-size: inherit; 99 | font-weight: inherit; 100 | } 101 | 102 | /* 103 | Reset links to optimize for opt-in styling instead of opt-out. 104 | */ 105 | 106 | a { 107 | color: inherit; 108 | text-decoration: inherit; 109 | } 110 | 111 | /* 112 | Add the correct font weight in Edge and Safari. 113 | */ 114 | 115 | b, 116 | strong { 117 | font-weight: bolder; 118 | } 119 | 120 | /* 121 | 1. Use the user's configured `mono` font family by default. 122 | 2. Correct the odd `em` font sizing in all browsers. 123 | */ 124 | 125 | code, 126 | kbd, 127 | samp, 128 | pre { 129 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 130 | /* 1 */ 131 | font-size: 1em; 132 | /* 2 */ 133 | } 134 | 135 | /* 136 | Add the correct font size in all browsers. 137 | */ 138 | 139 | small { 140 | font-size: 80%; 141 | } 142 | 143 | /* 144 | Prevent `sub` and `sup` elements from affecting the line height in all browsers. 145 | */ 146 | 147 | sub, 148 | sup { 149 | font-size: 75%; 150 | line-height: 0; 151 | position: relative; 152 | vertical-align: baseline; 153 | } 154 | 155 | sub { 156 | bottom: -0.25em; 157 | } 158 | 159 | sup { 160 | top: -0.5em; 161 | } 162 | 163 | /* 164 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) 165 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) 166 | 3. Remove gaps between table borders by default. 167 | */ 168 | 169 | table { 170 | text-indent: 0; 171 | /* 1 */ 172 | border-color: inherit; 173 | /* 2 */ 174 | border-collapse: collapse; 175 | /* 3 */ 176 | } 177 | 178 | /* 179 | 1. Change the font styles in all browsers. 180 | 2. Remove the margin in Firefox and Safari. 181 | 3. Remove default padding in all browsers. 182 | */ 183 | 184 | button, 185 | input, 186 | optgroup, 187 | select, 188 | textarea { 189 | font-family: inherit; 190 | /* 1 */ 191 | font-size: 100%; 192 | /* 1 */ 193 | font-weight: inherit; 194 | /* 1 */ 195 | line-height: inherit; 196 | /* 1 */ 197 | color: inherit; 198 | /* 1 */ 199 | margin: 0; 200 | /* 2 */ 201 | padding: 0; 202 | /* 3 */ 203 | } 204 | 205 | /* 206 | Remove the inheritance of text transform in Edge and Firefox. 207 | */ 208 | 209 | button, 210 | select { 211 | text-transform: none; 212 | } 213 | 214 | /* 215 | 1. Correct the inability to style clickable types in iOS and Safari. 216 | 2. Remove default button styles. 217 | */ 218 | 219 | button, 220 | [type='button'], 221 | [type='reset'], 222 | [type='submit'] { 223 | -webkit-appearance: button; 224 | /* 1 */ 225 | background-color: transparent; 226 | /* 2 */ 227 | background-image: none; 228 | /* 2 */ 229 | } 230 | 231 | /* 232 | Use the modern Firefox focus style for all focusable elements. 233 | */ 234 | 235 | :-moz-focusring { 236 | outline: auto; 237 | } 238 | 239 | /* 240 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) 241 | */ 242 | 243 | :-moz-ui-invalid { 244 | box-shadow: none; 245 | } 246 | 247 | /* 248 | Add the correct vertical alignment in Chrome and Firefox. 249 | */ 250 | 251 | progress { 252 | vertical-align: baseline; 253 | } 254 | 255 | /* 256 | Correct the cursor style of increment and decrement buttons in Safari. 257 | */ 258 | 259 | ::-webkit-inner-spin-button, 260 | ::-webkit-outer-spin-button { 261 | height: auto; 262 | } 263 | 264 | /* 265 | 1. Correct the odd appearance in Chrome and Safari. 266 | 2. Correct the outline style in Safari. 267 | */ 268 | 269 | [type='search'] { 270 | -webkit-appearance: textfield; 271 | /* 1 */ 272 | outline-offset: -2px; 273 | /* 2 */ 274 | } 275 | 276 | /* 277 | Remove the inner padding in Chrome and Safari on macOS. 278 | */ 279 | 280 | ::-webkit-search-decoration { 281 | -webkit-appearance: none; 282 | } 283 | 284 | /* 285 | 1. Correct the inability to style clickable types in iOS and Safari. 286 | 2. Change font properties to `inherit` in Safari. 287 | */ 288 | 289 | ::-webkit-file-upload-button { 290 | -webkit-appearance: button; 291 | /* 1 */ 292 | font: inherit; 293 | /* 2 */ 294 | } 295 | 296 | /* 297 | Add the correct display in Chrome and Safari. 298 | */ 299 | 300 | summary { 301 | display: list-item; 302 | } 303 | 304 | /* 305 | Removes the default spacing and border for appropriate elements. 306 | */ 307 | 308 | blockquote, 309 | dl, 310 | dd, 311 | h1, 312 | h2, 313 | h3, 314 | h4, 315 | h5, 316 | h6, 317 | hr, 318 | figure, 319 | p, 320 | pre { 321 | margin: 0; 322 | } 323 | 324 | fieldset { 325 | margin: 0; 326 | padding: 0; 327 | } 328 | 329 | legend { 330 | padding: 0; 331 | } 332 | 333 | ol, 334 | ul, 335 | menu { 336 | list-style: none; 337 | margin: 0; 338 | padding: 0; 339 | } 340 | 341 | /* 342 | Prevent resizing textareas horizontally by default. 343 | */ 344 | 345 | textarea { 346 | resize: vertical; 347 | } 348 | 349 | /* 350 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) 351 | 2. Set the default placeholder color to the user's configured gray 400 color. 352 | */ 353 | 354 | input::-moz-placeholder, textarea::-moz-placeholder { 355 | opacity: 1; 356 | /* 1 */ 357 | color: #9ca3af; 358 | /* 2 */ 359 | } 360 | 361 | input::placeholder, 362 | textarea::placeholder { 363 | opacity: 1; 364 | /* 1 */ 365 | color: #9ca3af; 366 | /* 2 */ 367 | } 368 | 369 | /* 370 | Set the default cursor for buttons. 371 | */ 372 | 373 | button, 374 | [role="button"] { 375 | cursor: pointer; 376 | } 377 | 378 | /* 379 | Make sure disabled buttons don't get the pointer cursor. 380 | */ 381 | 382 | :disabled { 383 | cursor: default; 384 | } 385 | 386 | /* 387 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) 388 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) 389 | This can trigger a poorly considered lint error in some tools but is included by design. 390 | */ 391 | 392 | img, 393 | svg, 394 | video, 395 | canvas, 396 | audio, 397 | iframe, 398 | embed, 399 | object { 400 | display: block; 401 | /* 1 */ 402 | vertical-align: middle; 403 | /* 2 */ 404 | } 405 | 406 | /* 407 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) 408 | */ 409 | 410 | img, 411 | video { 412 | max-width: 100%; 413 | height: auto; 414 | } 415 | 416 | /* Make elements with the HTML hidden attribute stay hidden by default */ 417 | 418 | [hidden] { 419 | display: none; 420 | } 421 | 422 | *, ::before, ::after { 423 | --tw-border-spacing-x: 0; 424 | --tw-border-spacing-y: 0; 425 | --tw-translate-x: 0; 426 | --tw-translate-y: 0; 427 | --tw-rotate: 0; 428 | --tw-skew-x: 0; 429 | --tw-skew-y: 0; 430 | --tw-scale-x: 1; 431 | --tw-scale-y: 1; 432 | --tw-pan-x: ; 433 | --tw-pan-y: ; 434 | --tw-pinch-zoom: ; 435 | --tw-scroll-snap-strictness: proximity; 436 | --tw-ordinal: ; 437 | --tw-slashed-zero: ; 438 | --tw-numeric-figure: ; 439 | --tw-numeric-spacing: ; 440 | --tw-numeric-fraction: ; 441 | --tw-ring-inset: ; 442 | --tw-ring-offset-width: 0px; 443 | --tw-ring-offset-color: #fff; 444 | --tw-ring-color: rgb(59 130 246 / 0.5); 445 | --tw-ring-offset-shadow: 0 0 #0000; 446 | --tw-ring-shadow: 0 0 #0000; 447 | --tw-shadow: 0 0 #0000; 448 | --tw-shadow-colored: 0 0 #0000; 449 | --tw-blur: ; 450 | --tw-brightness: ; 451 | --tw-contrast: ; 452 | --tw-grayscale: ; 453 | --tw-hue-rotate: ; 454 | --tw-invert: ; 455 | --tw-saturate: ; 456 | --tw-sepia: ; 457 | --tw-drop-shadow: ; 458 | --tw-backdrop-blur: ; 459 | --tw-backdrop-brightness: ; 460 | --tw-backdrop-contrast: ; 461 | --tw-backdrop-grayscale: ; 462 | --tw-backdrop-hue-rotate: ; 463 | --tw-backdrop-invert: ; 464 | --tw-backdrop-opacity: ; 465 | --tw-backdrop-saturate: ; 466 | --tw-backdrop-sepia: ; 467 | } 468 | 469 | ::backdrop { 470 | --tw-border-spacing-x: 0; 471 | --tw-border-spacing-y: 0; 472 | --tw-translate-x: 0; 473 | --tw-translate-y: 0; 474 | --tw-rotate: 0; 475 | --tw-skew-x: 0; 476 | --tw-skew-y: 0; 477 | --tw-scale-x: 1; 478 | --tw-scale-y: 1; 479 | --tw-pan-x: ; 480 | --tw-pan-y: ; 481 | --tw-pinch-zoom: ; 482 | --tw-scroll-snap-strictness: proximity; 483 | --tw-ordinal: ; 484 | --tw-slashed-zero: ; 485 | --tw-numeric-figure: ; 486 | --tw-numeric-spacing: ; 487 | --tw-numeric-fraction: ; 488 | --tw-ring-inset: ; 489 | --tw-ring-offset-width: 0px; 490 | --tw-ring-offset-color: #fff; 491 | --tw-ring-color: rgb(59 130 246 / 0.5); 492 | --tw-ring-offset-shadow: 0 0 #0000; 493 | --tw-ring-shadow: 0 0 #0000; 494 | --tw-shadow: 0 0 #0000; 495 | --tw-shadow-colored: 0 0 #0000; 496 | --tw-blur: ; 497 | --tw-brightness: ; 498 | --tw-contrast: ; 499 | --tw-grayscale: ; 500 | --tw-hue-rotate: ; 501 | --tw-invert: ; 502 | --tw-saturate: ; 503 | --tw-sepia: ; 504 | --tw-drop-shadow: ; 505 | --tw-backdrop-blur: ; 506 | --tw-backdrop-brightness: ; 507 | --tw-backdrop-contrast: ; 508 | --tw-backdrop-grayscale: ; 509 | --tw-backdrop-hue-rotate: ; 510 | --tw-backdrop-invert: ; 511 | --tw-backdrop-opacity: ; 512 | --tw-backdrop-saturate: ; 513 | --tw-backdrop-sepia: ; 514 | } 515 | 516 | .static { 517 | position: static; 518 | } 519 | 520 | .fixed { 521 | position: fixed; 522 | } 523 | 524 | .top-0 { 525 | top: 0px; 526 | } 527 | 528 | .z-10 { 529 | z-index: 10; 530 | } 531 | 532 | .my-14 { 533 | margin-top: 3.5rem; 534 | margin-bottom: 3.5rem; 535 | } 536 | 537 | .mb-4 { 538 | margin-bottom: 1rem; 539 | } 540 | 541 | .mb-6 { 542 | margin-bottom: 1.5rem; 543 | } 544 | 545 | .ml-2 { 546 | margin-left: 0.5rem; 547 | } 548 | 549 | .ml-4 { 550 | margin-left: 1rem; 551 | } 552 | 553 | .ml-auto { 554 | margin-left: auto; 555 | } 556 | 557 | .mr-10 { 558 | margin-right: 2.5rem; 559 | } 560 | 561 | .mr-16 { 562 | margin-right: 4rem; 563 | } 564 | 565 | .mr-2 { 566 | margin-right: 0.5rem; 567 | } 568 | 569 | .mr-4 { 570 | margin-right: 1rem; 571 | } 572 | 573 | .mr-8 { 574 | margin-right: 2rem; 575 | } 576 | 577 | .mr-auto { 578 | margin-right: auto; 579 | } 580 | 581 | .mt-10 { 582 | margin-top: 2.5rem; 583 | } 584 | 585 | .mt-12 { 586 | margin-top: 3rem; 587 | } 588 | 589 | .mt-4 { 590 | margin-top: 1rem; 591 | } 592 | 593 | .mt-40 { 594 | margin-top: 10rem; 595 | } 596 | 597 | .flex { 598 | display: flex; 599 | } 600 | 601 | .h-10 { 602 | height: 2.5rem; 603 | } 604 | 605 | .h-16 { 606 | height: 4rem; 607 | } 608 | 609 | .h-24 { 610 | height: 6rem; 611 | } 612 | 613 | .h-32 { 614 | height: 8rem; 615 | } 616 | 617 | .w-40 { 618 | width: 10rem; 619 | } 620 | 621 | .w-9 { 622 | width: 2.25rem; 623 | } 624 | 625 | .w-full { 626 | width: 100%; 627 | } 628 | 629 | .max-w-3xl { 630 | max-width: 48rem; 631 | } 632 | 633 | .max-w-4xl { 634 | max-width: 56rem; 635 | } 636 | 637 | .grow { 638 | flex-grow: 1; 639 | } 640 | 641 | .flex-col { 642 | flex-direction: column; 643 | } 644 | 645 | .items-center { 646 | align-items: center; 647 | } 648 | 649 | .justify-center { 650 | justify-content: center; 651 | } 652 | 653 | .justify-between { 654 | justify-content: space-between; 655 | } 656 | 657 | .justify-evenly { 658 | justify-content: space-evenly; 659 | } 660 | 661 | .self-start { 662 | align-self: flex-start; 663 | } 664 | 665 | .self-end { 666 | align-self: flex-end; 667 | } 668 | 669 | .whitespace-nowrap { 670 | white-space: nowrap; 671 | } 672 | 673 | .rounded { 674 | border-radius: 0.25rem; 675 | } 676 | 677 | .rounded-lg { 678 | border-radius: 0.5rem; 679 | } 680 | 681 | .rounded-t-lg { 682 | border-top-left-radius: 0.5rem; 683 | border-top-right-radius: 0.5rem; 684 | } 685 | 686 | .border { 687 | border-width: 1px; 688 | } 689 | 690 | .bg-emerald-200 { 691 | --tw-bg-opacity: 1; 692 | background-color: rgb(167 243 208 / var(--tw-bg-opacity)); 693 | } 694 | 695 | .bg-emerald-700 { 696 | --tw-bg-opacity: 1; 697 | background-color: rgb(4 120 87 / var(--tw-bg-opacity)); 698 | } 699 | 700 | .bg-green-700 { 701 | --tw-bg-opacity: 1; 702 | background-color: rgb(21 128 61 / var(--tw-bg-opacity)); 703 | } 704 | 705 | .bg-slate-300 { 706 | --tw-bg-opacity: 1; 707 | background-color: rgb(203 213 225 / var(--tw-bg-opacity)); 708 | } 709 | 710 | .bg-slate-50 { 711 | --tw-bg-opacity: 1; 712 | background-color: rgb(248 250 252 / var(--tw-bg-opacity)); 713 | } 714 | 715 | .p-3 { 716 | padding: 0.75rem; 717 | } 718 | 719 | .px-10 { 720 | padding-left: 2.5rem; 721 | padding-right: 2.5rem; 722 | } 723 | 724 | .px-2 { 725 | padding-left: 0.5rem; 726 | padding-right: 0.5rem; 727 | } 728 | 729 | .px-3 { 730 | padding-left: 0.75rem; 731 | padding-right: 0.75rem; 732 | } 733 | 734 | .py-1 { 735 | padding-top: 0.25rem; 736 | padding-bottom: 0.25rem; 737 | } 738 | 739 | .pb-8 { 740 | padding-bottom: 2rem; 741 | } 742 | 743 | .text-center { 744 | text-align: center; 745 | } 746 | 747 | .font-emoji { 748 | font-family: Material Icons, sans-serif; 749 | } 750 | 751 | .text-2xl { 752 | font-size: 1.5rem; 753 | line-height: 2rem; 754 | } 755 | 756 | .text-4xl { 757 | font-size: 2.25rem; 758 | line-height: 2.5rem; 759 | } 760 | 761 | .text-lg { 762 | font-size: 1.125rem; 763 | line-height: 1.75rem; 764 | } 765 | 766 | .font-bold { 767 | font-weight: 700; 768 | } 769 | 770 | .tracking-wider { 771 | letter-spacing: 0.05em; 772 | } 773 | 774 | .text-emerald-600 { 775 | --tw-text-opacity: 1; 776 | color: rgb(5 150 105 / var(--tw-text-opacity)); 777 | } 778 | 779 | .text-slate-50 { 780 | --tw-text-opacity: 1; 781 | color: rgb(248 250 252 / var(--tw-text-opacity)); 782 | } 783 | 784 | .text-white { 785 | --tw-text-opacity: 1; 786 | color: rgb(255 255 255 / var(--tw-text-opacity)); 787 | } 788 | 789 | .shadow { 790 | --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 791 | --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); 792 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 793 | } 794 | 795 | .shadow-lg { 796 | --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); 797 | --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); 798 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 799 | } 800 | 801 | body { 802 | font-family: Ubuntu, sans-serif; 803 | background-color: #eeeeee; 804 | background-image: url("data:image/svg+xml,%3Csvg width='52' height='26' viewBox='0 0 52 26' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23aeb8af' fill-opacity='0.4'%3E%3Cpath d='M10 10c0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4v2c-3.314 0-6-2.686-6-6 0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6zm25.464-1.95l8.486 8.486-1.414 1.414-8.486-8.486 1.414-1.414z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); 805 | } 806 | 807 | .hover\:bg-green-800:hover { 808 | --tw-bg-opacity: 1; 809 | background-color: rgb(22 101 52 / var(--tw-bg-opacity)); 810 | } 811 | 812 | .hover\:bg-slate-400:hover { 813 | --tw-bg-opacity: 1; 814 | background-color: rgb(148 163 184 / var(--tw-bg-opacity)); 815 | } 816 | -------------------------------------------------------------------------------- /app/normal-giropops-senhas/static/css/styles.css: -------------------------------------------------------------------------------- 1 | /* styles.css */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | body { 7 | font-family: Ubuntu, sans-serif; 8 | background-color: #eeeeee; 9 | background-image: url("data:image/svg+xml,%3Csvg width='52' height='26' viewBox='0 0 52 26' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23aeb8af' fill-opacity='0.4'%3E%3Cpath d='M10 10c0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4v2c-3.314 0-6-2.686-6-6 0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6zm25.464-1.95l8.486 8.486-1.414 1.414-8.486-8.486 1.414-1.414z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); 10 | } 11 | -------------------------------------------------------------------------------- /app/normal-giropops-senhas/static/js/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function showSenha() { 4 | const input = document.getElementById("senha"); 5 | const senhaIcon = document.getElementById("senha-icon"); 6 | if (input.attributes.type.nodeValue == "password") { 7 | input.setAttribute("type", "text"); 8 | senhaIcon.innerText = "visibility_off"; 9 | } else { 10 | input.setAttribute("type", "password"); 11 | senhaIcon.innerText = "visibility"; 12 | } 13 | } 14 | function showSenhaPorId(id) { 15 | console.log(id); 16 | const input = document.getElementById("senha-" + id); 17 | const senhaIcon = document.getElementById("senha-icon-" + id); 18 | if (input.attributes.type.nodeValue == "password") { 19 | input.setAttribute("type", "text"); 20 | senhaIcon.innerText = "visibility_off"; 21 | } else { 22 | input.setAttribute("type", "password"); 23 | senhaIcon.innerText = "visibility"; 24 | } 25 | } 26 | function copiarParaAreaDeTransferencia() { 27 | const senhaElemento = document.getElementById("senha"); 28 | navigator.clipboard.writeText(senhaElemento.value).then( 29 | () => { 30 | alert("Senha copiada para a área de transferência!"); 31 | }, 32 | (err) => { 33 | alert("Não foi possível copiar a senha: " + err); 34 | } 35 | ); 36 | } 37 | 38 | function copiarParaAreaDeTransferenciaPorId(id) { 39 | const senhaElemento = document.getElementById("senha-" + id); 40 | navigator.clipboard.writeText(senhaElemento.value).then( 41 | () => { 42 | alert("Senha copiada para a área de transferência!"); 43 | }, 44 | (err) => { 45 | alert("Não foi possível copiar a senha: " + err); 46 | } 47 | ); 48 | } 49 | 50 | function toggleUsuarios() { 51 | const listaUsuariosContainer = document.getElementById( 52 | "lista-usuarios-container" 53 | ); 54 | listaUsuariosContainer.classList.toggle("hidden"); 55 | } 56 | function buscarUltimasSenhas() { 57 | navigation.reload(); 58 | } 59 | -------------------------------------------------------------------------------- /app/normal-giropops-senhas/static/linuxtips-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/app/normal-giropops-senhas/static/linuxtips-logo.png -------------------------------------------------------------------------------- /app/normal-giropops-senhas/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./templates/*.html"], 4 | theme: { 5 | fontFamily: { 6 | emoji: ["Material Icons", "sans-serif"], 7 | }, 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /app/normal-giropops-senhas/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gerador de Senhas 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 25 |
26 |
27 |
28 |
29 |
30 |

Gerar senha

31 |
32 |
33 |
34 | 35 | 36 | 8 37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 | 45 |
46 | 50 |
51 | {% if senha %} 52 |
53 |

Senha gerada:

54 | 56 |
57 | 61 | 65 |
66 | {% endif %} 67 |
68 |
69 |
70 |
71 |
72 |
73 |

74 | Últimas senhas criadas 75 |

76 |
77 | 82 | {% include 'lista_senhas.html' %} 83 |
84 |
85 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /app/normal-giropops-senhas/templates/lista_senhas.html: -------------------------------------------------------------------------------- 1 |
2 | 35 |
36 | -------------------------------------------------------------------------------- /docs/images/AWS-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/AWS-keys.png -------------------------------------------------------------------------------- /docs/images/Arquitetura-Infra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/Arquitetura-Infra.png -------------------------------------------------------------------------------- /docs/images/acesso-giropops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/acesso-giropops.png -------------------------------------------------------------------------------- /docs/images/bot-firing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/bot-firing.png -------------------------------------------------------------------------------- /docs/images/bot-resolved.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/bot-resolved.png -------------------------------------------------------------------------------- /docs/images/build-distroless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/build-distroless.png -------------------------------------------------------------------------------- /docs/images/build-melange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/build-melange.png -------------------------------------------------------------------------------- /docs/images/build-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/build-normal.png -------------------------------------------------------------------------------- /docs/images/camadas-builddistroless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/camadas-builddistroless.png -------------------------------------------------------------------------------- /docs/images/camadas-buildmelange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/camadas-buildmelange.png -------------------------------------------------------------------------------- /docs/images/camadas-buildnormal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/camadas-buildnormal.png -------------------------------------------------------------------------------- /docs/images/crashloop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/crashloop.png -------------------------------------------------------------------------------- /docs/images/estrutura-helm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/estrutura-helm.png -------------------------------------------------------------------------------- /docs/images/false-signature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/false-signature.png -------------------------------------------------------------------------------- /docs/images/get-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/get-top.png -------------------------------------------------------------------------------- /docs/images/giropops-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/giropops-image.png -------------------------------------------------------------------------------- /docs/images/grafana-alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/grafana-alert.png -------------------------------------------------------------------------------- /docs/images/grafana-giropops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/grafana-giropops.png -------------------------------------------------------------------------------- /docs/images/grafana-temporesposta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/grafana-temporesposta.png -------------------------------------------------------------------------------- /docs/images/harbor-configs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/harbor-configs.png -------------------------------------------------------------------------------- /docs/images/helm-install-giropops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/helm-install-giropops.png -------------------------------------------------------------------------------- /docs/images/imagem-ajustada.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/imagem-ajustada.png -------------------------------------------------------------------------------- /docs/images/keda-scaledown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/keda-scaledown.png -------------------------------------------------------------------------------- /docs/images/keda-working.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/keda-working.png -------------------------------------------------------------------------------- /docs/images/kyverno-bloqueios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/kyverno-bloqueios.png -------------------------------------------------------------------------------- /docs/images/locust-charts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/locust-charts.png -------------------------------------------------------------------------------- /docs/images/locust-grafana1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/locust-grafana1.png -------------------------------------------------------------------------------- /docs/images/locust-grafana2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/locust-grafana2.png -------------------------------------------------------------------------------- /docs/images/locust-statics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/locust-statics.png -------------------------------------------------------------------------------- /docs/images/locust-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/locust-test.png -------------------------------------------------------------------------------- /docs/images/melange-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/melange-keys.png -------------------------------------------------------------------------------- /docs/images/nginx-assinada.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/nginx-assinada.png -------------------------------------------------------------------------------- /docs/images/prom-target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/prom-target.png -------------------------------------------------------------------------------- /docs/images/redis-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/redis-image.png -------------------------------------------------------------------------------- /docs/images/scale-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/scale-deployment.png -------------------------------------------------------------------------------- /docs/images/scan-build-distroless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/scan-build-distroless.png -------------------------------------------------------------------------------- /docs/images/scan-build-melange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/scan-build-melange.png -------------------------------------------------------------------------------- /docs/images/scan-build-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/scan-build-normal.png -------------------------------------------------------------------------------- /docs/images/set-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/docs/images/set-env.png -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | // Definindo apenas variáveis necessárias. Todo restante pode ser modificado diretamente neste arquivo 2 | variable "access_key" {} 3 | variable "secret_key" {} 4 | variable "k8s_subnet_cidr" { 5 | default = "172.31.112.0/20" // Altere esse valor para o correspondente com sua VPC 6 | } 7 | variable "k8s_subnet_cidr_2" { 8 | default = "172.31.128.0/20" // Altere esse valor para o correspondente com sua VPC 9 | } 10 | 11 | provider "aws" { 12 | region = "us-east-1" 13 | access_key = var.access_key 14 | secret_key = var.secret_key 15 | } 16 | 17 | terraform { 18 | backend "s3" { 19 | bucket = "fabiobartoli-k8s-pick-bucket" // Passe o nome do Bucket que você criou para armazenar o state 20 | key = "terraform/k8s-PICK-state" 21 | region = "us-east-1" 22 | } 23 | } 24 | 25 | module "k8s_provisioner" { 26 | source = "./modules/k8s_provisioner" 27 | ami = "ami-04b70fa74e45c3917" // Ubuntu Server 24.04 LTS (HVM), SSD Volume Type 28 | cp_instance_type = "t3a.small" // 2vCPU x 2Gib Memory 29 | instance_type = "t3a.small" // 2vCPU x 2Gib Memory 30 | volume_size = 64 31 | instance_count = 4 // Número de instâncias 32 | vpc_id = "vpc-096357cb7db323b17" // ID da sua VPC 33 | k8s_subnet_cidr = var.k8s_subnet_cidr 34 | k8s_subnet_cidr_2 = var.k8s_subnet_cidr_2 35 | k8s_subnet_az = "us-east-1a" // AZ para a subnet que será criada 36 | k8s_subnet_az_2 = "us-east-1b" // AZ para a subnet secundário para o Balancer 37 | AWS_ACCESS_KEY_ID = var.access_key 38 | AWS_SECRET_ACCESS_KEY = var.secret_key 39 | private_key = file("${path.module}/id_rsa") 40 | public_key = file("${path.module}/id_rsa.pub") 41 | security_group_rules = [ 42 | // PORTAS NECESSÁRIAS PARA O K8S 43 | { 44 | protocol = "tcp" 45 | from_port = 6443 46 | to_port = 6443 47 | cidr_blocks = ["0.0.0.0/0"] //Acessar o Kubeconfig remotamente 48 | }, 49 | { 50 | protocol = "tcp" 51 | from_port = 80 52 | to_port = 80 53 | cidr_blocks = ["0.0.0.0/0"] //Acessar o Kubeconfig remotamente 54 | }, 55 | { 56 | protocol = "tcp" 57 | from_port = 443 58 | to_port = 443 59 | cidr_blocks = ["0.0.0.0/0"] //Acessar o Kubeconfig remotamente 60 | }, 61 | { 62 | protocol = "tcp" 63 | from_port = 30443 64 | to_port = 30443 65 | cidr_blocks = ["0.0.0.0/0"] //Acessar o Nodeport 66 | }, 67 | { 68 | protocol = "tcp" 69 | from_port = 22 70 | to_port = 22 71 | cidr_blocks = ["0.0.0.0/0"] //SSH para o Terraform poder usar 72 | }, 73 | //LIBERA TODAS PORTAS PRA COMUNICAÇÃO ENTRE O CLUSTER 74 | { 75 | protocol = "-1" 76 | from_port = 0 77 | to_port = 0 78 | cidr_blocks = [var.k8s_subnet_cidr] 79 | } 80 | ] 81 | } 82 | 83 | output "control_plane_public_ip" { 84 | value = module.k8s_provisioner.control_plane_public_ip 85 | } 86 | 87 | output "worker_public_ips" { 88 | value = module.k8s_provisioner.worker_public_ips 89 | } 90 | 91 | output "k8s_alb_dns_name" { 92 | value = module.k8s_provisioner.k8s_alb_dns_name 93 | } -------------------------------------------------------------------------------- /manifests/helm/giropops-app/giropops-chart-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/manifests/helm/giropops-app/giropops-chart-0.1.0.tgz -------------------------------------------------------------------------------- /manifests/helm/giropops-app/helm-giropops-app/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: giropops-chart 3 | description: Helm chart para criar os Deployments, Services e ServiceAccounts do giropops 4 | 5 | version: 0.1.0 6 | appVersion: "1.0" 7 | 8 | maintainers: 9 | - name: Fabio Bartoli 10 | email: fabio.bartoli@outlook.com 11 | 12 | keywords: 13 | - ingress 14 | - giropops 15 | - redis 16 | 17 | sources: 18 | - https://github.com/FabioBartoli/LINUXtips-PICK/ 19 | 20 | dependencies: [] -------------------------------------------------------------------------------- /manifests/helm/giropops-app/helm-giropops-app/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- range $deployment := .Values.deployments }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: {{ $deployment.name }} 6 | namespace: {{ $deployment.namespace }} 7 | labels: 8 | env: {{ if $.Values.env }}{{ $.Values.env }}{{ else }}dev{{ end }} 9 | {{- range $key, $value := $deployment.labels }} 10 | {{ $key }}: {{ $value }} 11 | {{- end }} 12 | spec: 13 | replicas: {{ $deployment.replicas }} 14 | selector: 15 | matchLabels: 16 | app: {{ $deployment.labels.app }} 17 | template: 18 | metadata: 19 | labels: 20 | env: {{ if $.Values.env }}{{ $.Values.env }}{{ else }}dev{{ end }} 21 | {{- range $key, $value := $deployment.labels }} 22 | {{ $key }}: {{ $value }} 23 | {{- end }} 24 | spec: 25 | serviceAccountName: {{ $deployment.serviceAccountName }} 26 | {{- if $deployment.imagePullSecrets }} 27 | imagePullSecrets: 28 | {{- range $secret := $deployment.imagePullSecrets }} 29 | - name: {{ $secret.name }} 30 | {{- end }} 31 | {{- end }} 32 | containers: 33 | {{- range $container := $deployment.containers }} 34 | - name: {{ $container.name }} 35 | image: {{ $container.image }} 36 | ports: 37 | {{- range $port := $container.ports }} 38 | - containerPort: {{ $port.containerPort }} 39 | {{- end }} 40 | env: 41 | {{- range $env := $container.env }} 42 | - name: {{ $env.name }} 43 | value: {{ $env.value }} 44 | {{- end }} 45 | resources: 46 | limits: 47 | memory: {{ $container.resources.limits.memory }} 48 | cpu: {{ $container.resources.limits.cpu }} 49 | requests: 50 | memory: {{ $container.resources.requests.memory }} 51 | cpu: {{ $container.resources.requests.cpu }} 52 | securityContext: 53 | allowPrivilegeEscalation: {{ $container.securityContext.allowPrivilegeEscalation }} 54 | runAsUser: {{ $container.securityContext.runAsUser }} 55 | runAsGroup: {{ $container.securityContext.runAsGroup }} 56 | capabilities: 57 | drop: 58 | {{- range $capability := $container.securityContext.capabilities.drop }} 59 | - {{ $capability }} 60 | {{- end }} 61 | {{- if $container.securityContext.capabilities.add }} 62 | add: 63 | {{- range $capability := $container.securityContext.capabilities.add }} 64 | - {{ $capability }} 65 | {{- end }} 66 | {{- end }} 67 | livenessProbe: 68 | {{- toYaml $container.livenessProbe | nindent 12 }} 69 | readinessProbe: 70 | {{- toYaml $container.readinessProbe | nindent 12 }} 71 | {{- if $container.command }} 72 | command: 73 | {{- range $cmd := $container.command }} 74 | - {{ $cmd | quote }} 75 | {{- end }} 76 | {{- end }} 77 | {{- end }} 78 | --- 79 | {{- end }} -------------------------------------------------------------------------------- /manifests/helm/giropops-app/helm-giropops-app/templates/service.yaml: -------------------------------------------------------------------------------- 1 | {{- range $service := .Values.services }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ $service.name }} 6 | namespace: {{ $service.namespace | default "default" }} 7 | labels: 8 | app: {{ $service.selector.app }} 9 | spec: 10 | selector: 11 | app: {{ $service.selector.app }} 12 | ports: 13 | {{- range $port := $service.ports }} 14 | - protocol: {{ $port.protocol }} 15 | port: {{ $port.port }} 16 | targetPort: {{ $port.targetPort }} 17 | name: {{ $port.name | default "http" }} 18 | {{- end }} 19 | type: ClusterIP 20 | --- 21 | {{- end }} -------------------------------------------------------------------------------- /manifests/helm/giropops-app/helm-giropops-app/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- range $sa := .Values.serviceAccounts }} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ $sa.name }} 6 | namespace: {{ $sa.namespace | default "default" }} 7 | --- 8 | {{- end }} -------------------------------------------------------------------------------- /manifests/helm/giropops-app/helm-giropops-app/values.yaml: -------------------------------------------------------------------------------- 1 | deployments: 2 | - name: giropops-senhas 3 | namespace: giropops 4 | replicas: 2 5 | serviceAccountName: giropops-senhas-sa 6 | labels: 7 | app: giropops-senhas 8 | containers: 9 | - name: giropops-senhas 10 | image: harbor.fabiobartoli.com.br/pick2024/melange-giropops-senhas 11 | ports: 12 | - containerPort: 5000 13 | env: 14 | - name: REDIS_HOST 15 | value: redis-service 16 | resources: 17 | limits: 18 | memory: "256Mi" 19 | cpu: "500m" 20 | requests: 21 | memory: "128Mi" 22 | cpu: "250m" 23 | securityContext: 24 | allowPrivilegeEscalation: false 25 | runAsUser: 1000 26 | runAsGroup: 3000 27 | capabilities: 28 | drop: 29 | - ALL 30 | livenessProbe: 31 | httpGet: 32 | path: / 33 | port: 5000 34 | initialDelaySeconds: 10 35 | periodSeconds: 10 36 | timeoutSeconds: 5 37 | failureThreshold: 3 38 | readinessProbe: 39 | exec: 40 | command: 41 | - curl 42 | - -f 43 | - http://localhost:5000/ 44 | initialDelaySeconds: 10 45 | periodSeconds: 10 46 | timeoutSeconds: 5 47 | failureThreshold: 3 48 | successThreshold: 1 49 | imagePullSecrets: 50 | - name: regcred 51 | 52 | - name: redis-deployment 53 | namespace: giropops 54 | replicas: 1 55 | serviceAccountName: redis-sa 56 | labels: 57 | app: redis 58 | containers: 59 | - name: redis 60 | image: harbor.fabiobartoli.com.br/pick2024/redis 61 | ports: 62 | - containerPort: 6379 63 | resources: 64 | limits: 65 | memory: "256Mi" 66 | cpu: "500m" 67 | requests: 68 | memory: "128Mi" 69 | cpu: "250m" 70 | securityContext: 71 | runAsUser: 1000 72 | runAsGroup: 3000 73 | allowPrivilegeEscalation: false 74 | capabilities: 75 | drop: 76 | - ALL 77 | add: 78 | - NET_BIND_SERVICE 79 | - CHOWN 80 | - SETUID 81 | - SETGID 82 | livenessProbe: 83 | tcpSocket: 84 | port: 6379 85 | initialDelaySeconds: 30 86 | timeoutSeconds: 5 87 | periodSeconds: 5 88 | failureThreshold: 5 89 | successThreshold: 1 90 | readinessProbe: 91 | exec: 92 | command: 93 | - redis-cli 94 | - ping 95 | initialDelaySeconds: 20 96 | timeoutSeconds: 5 97 | periodSeconds: 3 98 | command: ["redis-server", "--appendonly", "no"] 99 | imagePullSecrets: 100 | - name: regcred 101 | 102 | services: 103 | - name: giropops-senhas 104 | namespace: giropops 105 | selector: 106 | app: giropops-senhas 107 | ports: 108 | - protocol: TCP 109 | port: 5000 110 | targetPort: 5000 111 | name: tcp-app 112 | 113 | - name: redis-service 114 | namespace: giropops 115 | selector: 116 | app: redis 117 | ports: 118 | - protocol: TCP 119 | port: 6379 120 | targetPort: 6379 121 | 122 | serviceAccounts: 123 | - name: giropops-senhas-sa 124 | namespace: giropops 125 | 126 | - name: redis-sa 127 | namespace: giropops -------------------------------------------------------------------------------- /manifests/helm/giropops-app/index.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | entries: 3 | giropops-chart: 4 | - apiVersion: v2 5 | appVersion: "1.0" 6 | created: "2024-08-26T21:07:08.194533872-03:00" 7 | description: Helm chart para criar os Deployments, Services e ServiceAccounts 8 | do giropops 9 | digest: 712e07ad91dae023aa2a6abc5e27067da237e11f7b723b1f803744418d237c40 10 | keywords: 11 | - ingress 12 | - giropops 13 | - redis 14 | maintainers: 15 | - email: fabio.bartoli@outlook.com 16 | name: Fabio Bartoli 17 | name: giropops-chart 18 | sources: 19 | - https://github.com/FabioBartoli/LINUXtips-PICK/ 20 | urls: 21 | - https://fabiobartoli.github.io/LINUXtips-PICK/manifests/helm/giropops-app/giropops-chart-0.1.0.tgz 22 | version: 0.1.0 23 | generated: "2024-08-26T21:07:08.194152363-03:00" 24 | -------------------------------------------------------------------------------- /manifests/helm/ingress/helm-pick-cluster-ingress/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: ingress-templates 3 | description: Helm chart reutilizável para configurar Ingresses no Kubernetes 4 | 5 | type: application 6 | 7 | version: 0.1.0 8 | appVersion: "1.0" 9 | 10 | maintainers: 11 | - name: Fabio Bartoli 12 | email: fabio.bartoli@outlook.com 13 | 14 | keywords: 15 | - ingress 16 | - nginx 17 | - kubernetes 18 | - helm 19 | - harbor 20 | - locust 21 | - kube-prometheus 22 | 23 | sources: 24 | - https://github.com/FabioBartoli/LINUXtips-PICK/ 25 | 26 | dependencies: [] 27 | 28 | -------------------------------------------------------------------------------- /manifests/helm/ingress/helm-pick-cluster-ingress/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- range $ingress := .Values.ingresses }} 2 | apiVersion: networking.k8s.io/v1 3 | kind: Ingress 4 | metadata: 5 | name: {{ $ingress.name }} 6 | namespace: {{ $ingress.namespace | default "default" }} 7 | annotations: 8 | {{- range $key, $value := $ingress.annotations }} 9 | {{ $key }}: "{{ $value }}" 10 | {{- end }} 11 | spec: 12 | ingressClassName: {{ $ingress.ingressClassName }} 13 | rules: 14 | - host: {{ $ingress.host }} 15 | http: 16 | paths: 17 | {{- range $path := $ingress.paths }} 18 | - path: {{ $path.path }} 19 | pathType: {{ $path.pathType }} 20 | backend: 21 | service: 22 | name: {{ $path.service.name }} 23 | port: 24 | number: {{ $path.service.port }} 25 | {{- end }} 26 | {{- if $ingress.tls }} 27 | tls: 28 | - hosts: 29 | {{- range $host := $ingress.tls.hosts }} 30 | - {{ $host }} 31 | {{- end }} 32 | secretName: {{ $ingress.tls.secretName }} 33 | {{- end }} 34 | --- 35 | {{- end }} -------------------------------------------------------------------------------- /manifests/helm/ingress/helm-pick-cluster-ingress/values.yaml: -------------------------------------------------------------------------------- 1 | ingresses: 2 | - name: giropops-senhas 3 | namespace: giropops 4 | ingressClassName: nginx 5 | host: giropops-senhas.fabiobartoli.com.br 6 | annotations: 7 | nginx.ingress.kubernetes.io/rewrite-target: / 8 | paths: 9 | - path: / 10 | pathType: Prefix 11 | service: 12 | name: giropops-senhas 13 | port: 5000 14 | 15 | - name: grafana 16 | namespace: monitoring 17 | ingressClassName: nginx 18 | host: grafana.fabiobartoli.com.br 19 | annotations: 20 | nginx.ingress.kubernetes.io/rewrite-target: / 21 | paths: 22 | - path: / 23 | pathType: Prefix 24 | service: 25 | name: kube-prometheus-grafana 26 | port: 80 27 | 28 | - name: harbor-ingress 29 | namespace: harbor 30 | ingressClassName: nginx 31 | host: harbor.fabiobartoli.com.br 32 | annotations: 33 | ingress.kubernetes.io/proxy-body-size: "0" 34 | ingress.kubernetes.io/ssl-redirect: "true" 35 | nginx.ingress.kubernetes.io/proxy-body-size: "0" 36 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 37 | paths: 38 | - path: /api/ 39 | pathType: Prefix 40 | service: 41 | name: harbor-harbor-core 42 | port: 80 43 | - path: /service/ 44 | pathType: Prefix 45 | service: 46 | name: harbor-harbor-core 47 | port: 80 48 | - path: /v2/ 49 | pathType: Prefix 50 | service: 51 | name: harbor-harbor-core 52 | port: 80 53 | - path: /chartrepo/ 54 | pathType: Prefix 55 | service: 56 | name: harbor-harbor-core 57 | port: 80 58 | - path: /c/ 59 | pathType: Prefix 60 | service: 61 | name: harbor-harbor-core 62 | port: 80 63 | - path: / 64 | pathType: Prefix 65 | service: 66 | name: harbor-harbor-portal 67 | port: 80 68 | 69 | - name: locust-giropops 70 | namespace: locust 71 | ingressClassName: nginx 72 | host: locust.fabiobartoli.com.br 73 | annotations: 74 | nginx.ingress.kubernetes.io/rewrite-target: / 75 | paths: 76 | - path: / 77 | pathType: Prefix 78 | service: 79 | name: locust-giropops 80 | port: 8089 81 | 82 | - name: kube-prometheus 83 | namespace: monitoring 84 | ingressClassName: nginx 85 | host: prometheus.fabiobartoli.com.br 86 | annotations: 87 | nginx.ingress.kubernetes.io/rewrite-target: / 88 | paths: 89 | - path: / 90 | pathType: Prefix 91 | service: 92 | name: kube-prometheus-kube-prome-prometheus 93 | port: 9090 94 | -------------------------------------------------------------------------------- /manifests/helm/ingress/index.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | entries: 3 | ingress-templates: 4 | - apiVersion: v2 5 | appVersion: "1.0" 6 | created: "2024-08-26T18:05:33.890213211-03:00" 7 | description: Helm chart reutilizável para configurar Ingresses no Kubernetes 8 | digest: 5620153968b1839823cd7a89d48bb5e6d6c782a68cc09f67dbd95179605f1a26 9 | keywords: 10 | - ingress 11 | - nginx 12 | - kubernetes 13 | - helm 14 | - harbor 15 | - locust 16 | - kube-prometheus 17 | maintainers: 18 | - email: fabio.bartoli@outlook.com 19 | name: Fabio Bartoli 20 | name: ingress-templates 21 | sources: 22 | - https://github.com/FabioBartoli/LINUXtips-PICK/ 23 | type: application 24 | urls: 25 | - https://fabiobartoli.github.io/LINUXtips-PICK/manifests/helm/ingress/ingress-templates-0.1.0.tgz 26 | version: 0.1.0 27 | generated: "2024-08-26T18:05:33.889914709-03:00" 28 | -------------------------------------------------------------------------------- /manifests/helm/ingress/ingress-templates-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabioBartoli/LINUXtips-PICK/69b38d18f83e4e3fbd86e9ca07452a96b7e93de9/manifests/helm/ingress/ingress-templates-0.1.0.tgz -------------------------------------------------------------------------------- /manifests/locust/locust-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | locustfile.py: |- 4 | from locust import HttpUser, task, between 5 | 6 | class Giropops(HttpUser): 7 | wait_time = between(1, 2) 8 | 9 | @task(1) 10 | def gerar_senha(self): 11 | self.client.post("/api/gerar-senha", json={"tamanho": 8, "incluir_numeros": True, "incluir_caracteres_especiais": True}) 12 | 13 | @task(2) 14 | def listar_senha(self): 15 | self.client.get("/api/senhas") 16 | kind: ConfigMap 17 | metadata: 18 | name: locust-scripts 19 | namespace: locust 20 | -------------------------------------------------------------------------------- /manifests/locust/locust-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: locust-giropops 6 | name: locust-giropops 7 | namespace: locust 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: locust-giropops 13 | template: 14 | metadata: 15 | labels: 16 | app: locust-giropops 17 | spec: 18 | containers: 19 | - image: linuxtips/locust-giropops:1.0 20 | name: locust-giropops 21 | env: 22 | - name: LOCUST_LOCUSTFILE 23 | value: "/usr/src/app/scripts/locustfile.py" 24 | ports: 25 | - containerPort: 8089 26 | imagePullPolicy: Always 27 | volumeMounts: 28 | - name: locust-scripts 29 | mountPath: /usr/src/app/scripts 30 | securityContext: 31 | allowPrivilegeEscalation: false 32 | runAsUser: 1000 33 | runAsGroup: 3000 34 | volumes: 35 | - name: locust-scripts 36 | configMap: 37 | name: locust-scripts 38 | optional: true 39 | -------------------------------------------------------------------------------- /manifests/locust/locust-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: locust-giropops 5 | namespace: locust 6 | spec: 7 | selector: 8 | app: locust-giropops 9 | ports: 10 | - protocol: TCP 11 | port: 80 12 | targetPort: 8089 13 | type: ClusterIP 14 | -------------------------------------------------------------------------------- /manifests/metrics-hpa/KEDA-scaledObjectRedis.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: keda.sh/v1alpha1 2 | kind: ScaledObject 3 | metadata: 4 | name: redis-scaledobject 5 | namespace: giropops 6 | spec: 7 | scaleTargetRef: 8 | kind: Deployment 9 | name: redis-deployment 10 | minReplicaCount: 1 11 | maxReplicaCount: 10 12 | cooldownPeriod: 60 13 | triggers: 14 | - type: prometheus 15 | metadata: 16 | serverAddress: http://kube-prometheus-kube-prome-prometheus.monitoring.svc.cluster.local:9090/ 17 | metricName: giropops_senhas_replicas 18 | threshold: '3' 19 | query: | 20 | sum(kube_deployment_status_replicas{namespace="giropops",deployment="giropops-senhas"}) -------------------------------------------------------------------------------- /manifests/metrics-hpa/hpa-giropops.senhas.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: giropops-senhas-hpa 5 | namespace: giropops 6 | spec: 7 | scaleTargetRef: 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | name: giropops-senhas 11 | minReplicas: 2 12 | maxReplicas: 10 13 | metrics: 14 | - type: Resource 15 | resource: 16 | name: cpu 17 | target: 18 | type: Utilization 19 | averageUtilization: 50 20 | - type: Resource 21 | resource: 22 | name: memory 23 | target: 24 | type: Utilization 25 | averageUtilization: 50 26 | behavior: 27 | scaleUp: 28 | stabilizationWindowSeconds: 5 29 | policies: 30 | - type: Percent 31 | value: 100 32 | periodSeconds: 10 33 | scaleDown: 34 | stabilizationWindowSeconds: 60 35 | policies: 36 | - type: Percent 37 | value: 100 38 | periodSeconds: 10 39 | -------------------------------------------------------------------------------- /manifests/metrics-hpa/metrics-components.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | k8s-app: metrics-server 6 | name: metrics-server 7 | namespace: kube-system 8 | --- 9 | apiVersion: rbac.authorization.k8s.io/v1 10 | kind: ClusterRole 11 | metadata: 12 | labels: 13 | k8s-app: metrics-server 14 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 15 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 16 | rbac.authorization.k8s.io/aggregate-to-view: "true" 17 | name: system:aggregated-metrics-reader 18 | rules: 19 | - apiGroups: 20 | - metrics.k8s.io 21 | resources: 22 | - pods 23 | - nodes 24 | verbs: 25 | - get 26 | - list 27 | - watch 28 | --- 29 | apiVersion: rbac.authorization.k8s.io/v1 30 | kind: ClusterRole 31 | metadata: 32 | labels: 33 | k8s-app: metrics-server 34 | name: system:metrics-server 35 | rules: 36 | - apiGroups: 37 | - "" 38 | resources: 39 | - nodes/metrics 40 | verbs: 41 | - get 42 | - apiGroups: 43 | - "" 44 | resources: 45 | - pods 46 | - nodes 47 | verbs: 48 | - get 49 | - list 50 | - watch 51 | --- 52 | apiVersion: rbac.authorization.k8s.io/v1 53 | kind: RoleBinding 54 | metadata: 55 | labels: 56 | k8s-app: metrics-server 57 | name: metrics-server-auth-reader 58 | namespace: kube-system 59 | roleRef: 60 | apiGroup: rbac.authorization.k8s.io 61 | kind: Role 62 | name: extension-apiserver-authentication-reader 63 | subjects: 64 | - kind: ServiceAccount 65 | name: metrics-server 66 | namespace: kube-system 67 | --- 68 | apiVersion: rbac.authorization.k8s.io/v1 69 | kind: ClusterRoleBinding 70 | metadata: 71 | labels: 72 | k8s-app: metrics-server 73 | name: metrics-server:system:auth-delegator 74 | roleRef: 75 | apiGroup: rbac.authorization.k8s.io 76 | kind: ClusterRole 77 | name: system:auth-delegator 78 | subjects: 79 | - kind: ServiceAccount 80 | name: metrics-server 81 | namespace: kube-system 82 | --- 83 | apiVersion: rbac.authorization.k8s.io/v1 84 | kind: ClusterRoleBinding 85 | metadata: 86 | labels: 87 | k8s-app: metrics-server 88 | name: system:metrics-server 89 | roleRef: 90 | apiGroup: rbac.authorization.k8s.io 91 | kind: ClusterRole 92 | name: system:metrics-server 93 | subjects: 94 | - kind: ServiceAccount 95 | name: metrics-server 96 | namespace: kube-system 97 | --- 98 | apiVersion: v1 99 | kind: Service 100 | metadata: 101 | labels: 102 | k8s-app: metrics-server 103 | name: metrics-server 104 | namespace: kube-system 105 | spec: 106 | ports: 107 | - name: https 108 | port: 443 109 | protocol: TCP 110 | targetPort: https 111 | selector: 112 | k8s-app: metrics-server 113 | --- 114 | apiVersion: apps/v1 115 | kind: Deployment 116 | metadata: 117 | labels: 118 | k8s-app: metrics-server 119 | name: metrics-server 120 | namespace: kube-system 121 | spec: 122 | selector: 123 | matchLabels: 124 | k8s-app: metrics-server 125 | strategy: 126 | rollingUpdate: 127 | maxUnavailable: 0 128 | template: 129 | metadata: 130 | labels: 131 | k8s-app: metrics-server 132 | spec: 133 | containers: 134 | - args: 135 | - --cert-dir=/tmp 136 | - --secure-port=10250 137 | - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname 138 | - --kubelet-use-node-status-port 139 | - --metric-resolution=15s 140 | - --kubelet-insecure-tls 141 | image: registry.k8s.io/metrics-server/metrics-server:v0.7.1 142 | imagePullPolicy: IfNotPresent 143 | livenessProbe: 144 | failureThreshold: 3 145 | httpGet: 146 | path: /livez 147 | port: https 148 | scheme: HTTPS 149 | periodSeconds: 10 150 | name: metrics-server 151 | ports: 152 | - containerPort: 10250 153 | name: https 154 | protocol: TCP 155 | readinessProbe: 156 | failureThreshold: 3 157 | httpGet: 158 | path: /readyz 159 | port: https 160 | scheme: HTTPS 161 | initialDelaySeconds: 20 162 | periodSeconds: 10 163 | resources: 164 | requests: 165 | cpu: 100m 166 | memory: 200Mi 167 | securityContext: 168 | allowPrivilegeEscalation: false 169 | capabilities: 170 | drop: 171 | - ALL 172 | readOnlyRootFilesystem: true 173 | runAsNonRoot: true 174 | runAsUser: 1000 175 | seccompProfile: 176 | type: RuntimeDefault 177 | volumeMounts: 178 | - mountPath: /tmp 179 | name: tmp-dir 180 | nodeSelector: 181 | kubernetes.io/os: linux 182 | priorityClassName: system-cluster-critical 183 | serviceAccountName: metrics-server 184 | volumes: 185 | - emptyDir: {} 186 | name: tmp-dir 187 | --- 188 | apiVersion: apiregistration.k8s.io/v1 189 | kind: APIService 190 | metadata: 191 | labels: 192 | k8s-app: metrics-server 193 | name: v1beta1.metrics.k8s.io 194 | spec: 195 | group: metrics.k8s.io 196 | groupPriorityMinimum: 100 197 | insecureSkipTLSVerify: true 198 | service: 199 | name: metrics-server 200 | namespace: kube-system 201 | version: v1beta1 202 | versionPriority: 100 -------------------------------------------------------------------------------- /modules/k8s_provisioner/ingress-deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | app.kubernetes.io/instance: ingress-nginx 6 | app.kubernetes.io/name: ingress-nginx 7 | name: ingress-nginx 8 | --- 9 | apiVersion: v1 10 | automountServiceAccountToken: true 11 | kind: ServiceAccount 12 | metadata: 13 | labels: 14 | app.kubernetes.io/component: controller 15 | app.kubernetes.io/instance: ingress-nginx 16 | app.kubernetes.io/name: ingress-nginx 17 | app.kubernetes.io/part-of: ingress-nginx 18 | app.kubernetes.io/version: 1.11.2 19 | name: ingress-nginx 20 | namespace: ingress-nginx 21 | --- 22 | apiVersion: v1 23 | automountServiceAccountToken: true 24 | kind: ServiceAccount 25 | metadata: 26 | labels: 27 | app.kubernetes.io/component: admission-webhook 28 | app.kubernetes.io/instance: ingress-nginx 29 | app.kubernetes.io/name: ingress-nginx 30 | app.kubernetes.io/part-of: ingress-nginx 31 | app.kubernetes.io/version: 1.11.2 32 | name: ingress-nginx-admission 33 | namespace: ingress-nginx 34 | --- 35 | apiVersion: rbac.authorization.k8s.io/v1 36 | kind: Role 37 | metadata: 38 | labels: 39 | app.kubernetes.io/component: controller 40 | app.kubernetes.io/instance: ingress-nginx 41 | app.kubernetes.io/name: ingress-nginx 42 | app.kubernetes.io/part-of: ingress-nginx 43 | app.kubernetes.io/version: 1.11.2 44 | name: ingress-nginx 45 | namespace: ingress-nginx 46 | rules: 47 | - apiGroups: 48 | - "" 49 | resources: 50 | - namespaces 51 | verbs: 52 | - get 53 | - apiGroups: 54 | - "" 55 | resources: 56 | - configmaps 57 | - pods 58 | - secrets 59 | - endpoints 60 | verbs: 61 | - get 62 | - list 63 | - watch 64 | - apiGroups: 65 | - "" 66 | resources: 67 | - services 68 | verbs: 69 | - get 70 | - list 71 | - watch 72 | - apiGroups: 73 | - networking.k8s.io 74 | resources: 75 | - ingresses 76 | verbs: 77 | - get 78 | - list 79 | - watch 80 | - apiGroups: 81 | - networking.k8s.io 82 | resources: 83 | - ingresses/status 84 | verbs: 85 | - update 86 | - apiGroups: 87 | - networking.k8s.io 88 | resources: 89 | - ingressclasses 90 | verbs: 91 | - get 92 | - list 93 | - watch 94 | - apiGroups: 95 | - coordination.k8s.io 96 | resourceNames: 97 | - ingress-nginx-leader 98 | resources: 99 | - leases 100 | verbs: 101 | - get 102 | - update 103 | - apiGroups: 104 | - coordination.k8s.io 105 | resources: 106 | - leases 107 | verbs: 108 | - create 109 | - apiGroups: 110 | - "" 111 | resources: 112 | - events 113 | verbs: 114 | - create 115 | - patch 116 | - apiGroups: 117 | - discovery.k8s.io 118 | resources: 119 | - endpointslices 120 | verbs: 121 | - list 122 | - watch 123 | - get 124 | --- 125 | apiVersion: rbac.authorization.k8s.io/v1 126 | kind: Role 127 | metadata: 128 | labels: 129 | app.kubernetes.io/component: admission-webhook 130 | app.kubernetes.io/instance: ingress-nginx 131 | app.kubernetes.io/name: ingress-nginx 132 | app.kubernetes.io/part-of: ingress-nginx 133 | app.kubernetes.io/version: 1.11.2 134 | name: ingress-nginx-admission 135 | namespace: ingress-nginx 136 | rules: 137 | - apiGroups: 138 | - "" 139 | resources: 140 | - secrets 141 | verbs: 142 | - get 143 | - create 144 | --- 145 | apiVersion: rbac.authorization.k8s.io/v1 146 | kind: ClusterRole 147 | metadata: 148 | labels: 149 | app.kubernetes.io/instance: ingress-nginx 150 | app.kubernetes.io/name: ingress-nginx 151 | app.kubernetes.io/part-of: ingress-nginx 152 | app.kubernetes.io/version: 1.11.2 153 | name: ingress-nginx 154 | rules: 155 | - apiGroups: 156 | - "" 157 | resources: 158 | - configmaps 159 | - endpoints 160 | - nodes 161 | - pods 162 | - secrets 163 | - namespaces 164 | verbs: 165 | - list 166 | - watch 167 | - apiGroups: 168 | - coordination.k8s.io 169 | resources: 170 | - leases 171 | verbs: 172 | - list 173 | - watch 174 | - apiGroups: 175 | - "" 176 | resources: 177 | - nodes 178 | verbs: 179 | - get 180 | - apiGroups: 181 | - "" 182 | resources: 183 | - services 184 | verbs: 185 | - get 186 | - list 187 | - watch 188 | - apiGroups: 189 | - networking.k8s.io 190 | resources: 191 | - ingresses 192 | verbs: 193 | - get 194 | - list 195 | - watch 196 | - apiGroups: 197 | - "" 198 | resources: 199 | - events 200 | verbs: 201 | - create 202 | - patch 203 | - apiGroups: 204 | - networking.k8s.io 205 | resources: 206 | - ingresses/status 207 | verbs: 208 | - update 209 | - apiGroups: 210 | - networking.k8s.io 211 | resources: 212 | - ingressclasses 213 | verbs: 214 | - get 215 | - list 216 | - watch 217 | - apiGroups: 218 | - discovery.k8s.io 219 | resources: 220 | - endpointslices 221 | verbs: 222 | - list 223 | - watch 224 | - get 225 | --- 226 | apiVersion: rbac.authorization.k8s.io/v1 227 | kind: ClusterRole 228 | metadata: 229 | labels: 230 | app.kubernetes.io/component: admission-webhook 231 | app.kubernetes.io/instance: ingress-nginx 232 | app.kubernetes.io/name: ingress-nginx 233 | app.kubernetes.io/part-of: ingress-nginx 234 | app.kubernetes.io/version: 1.11.2 235 | name: ingress-nginx-admission 236 | rules: 237 | - apiGroups: 238 | - admissionregistration.k8s.io 239 | resources: 240 | - validatingwebhookconfigurations 241 | verbs: 242 | - get 243 | - update 244 | --- 245 | apiVersion: rbac.authorization.k8s.io/v1 246 | kind: RoleBinding 247 | metadata: 248 | labels: 249 | app.kubernetes.io/component: controller 250 | app.kubernetes.io/instance: ingress-nginx 251 | app.kubernetes.io/name: ingress-nginx 252 | app.kubernetes.io/part-of: ingress-nginx 253 | app.kubernetes.io/version: 1.11.2 254 | name: ingress-nginx 255 | namespace: ingress-nginx 256 | roleRef: 257 | apiGroup: rbac.authorization.k8s.io 258 | kind: Role 259 | name: ingress-nginx 260 | subjects: 261 | - kind: ServiceAccount 262 | name: ingress-nginx 263 | namespace: ingress-nginx 264 | --- 265 | apiVersion: rbac.authorization.k8s.io/v1 266 | kind: RoleBinding 267 | metadata: 268 | labels: 269 | app.kubernetes.io/component: admission-webhook 270 | app.kubernetes.io/instance: ingress-nginx 271 | app.kubernetes.io/name: ingress-nginx 272 | app.kubernetes.io/part-of: ingress-nginx 273 | app.kubernetes.io/version: 1.11.2 274 | name: ingress-nginx-admission 275 | namespace: ingress-nginx 276 | roleRef: 277 | apiGroup: rbac.authorization.k8s.io 278 | kind: Role 279 | name: ingress-nginx-admission 280 | subjects: 281 | - kind: ServiceAccount 282 | name: ingress-nginx-admission 283 | namespace: ingress-nginx 284 | --- 285 | apiVersion: rbac.authorization.k8s.io/v1 286 | kind: ClusterRoleBinding 287 | metadata: 288 | labels: 289 | app.kubernetes.io/instance: ingress-nginx 290 | app.kubernetes.io/name: ingress-nginx 291 | app.kubernetes.io/part-of: ingress-nginx 292 | app.kubernetes.io/version: 1.11.2 293 | name: ingress-nginx 294 | roleRef: 295 | apiGroup: rbac.authorization.k8s.io 296 | kind: ClusterRole 297 | name: ingress-nginx 298 | subjects: 299 | - kind: ServiceAccount 300 | name: ingress-nginx 301 | namespace: ingress-nginx 302 | --- 303 | apiVersion: rbac.authorization.k8s.io/v1 304 | kind: ClusterRoleBinding 305 | metadata: 306 | labels: 307 | app.kubernetes.io/component: admission-webhook 308 | app.kubernetes.io/instance: ingress-nginx 309 | app.kubernetes.io/name: ingress-nginx 310 | app.kubernetes.io/part-of: ingress-nginx 311 | app.kubernetes.io/version: 1.11.2 312 | name: ingress-nginx-admission 313 | roleRef: 314 | apiGroup: rbac.authorization.k8s.io 315 | kind: ClusterRole 316 | name: ingress-nginx-admission 317 | subjects: 318 | - kind: ServiceAccount 319 | name: ingress-nginx-admission 320 | namespace: ingress-nginx 321 | --- 322 | apiVersion: v1 323 | data: 324 | allow-snippet-annotations: "false" 325 | kind: ConfigMap 326 | metadata: 327 | labels: 328 | app.kubernetes.io/component: controller 329 | app.kubernetes.io/instance: ingress-nginx 330 | app.kubernetes.io/name: ingress-nginx 331 | app.kubernetes.io/part-of: ingress-nginx 332 | app.kubernetes.io/version: 1.11.2 333 | name: ingress-nginx-controller 334 | namespace: ingress-nginx 335 | --- 336 | apiVersion: v1 337 | kind: Service 338 | metadata: 339 | labels: 340 | app.kubernetes.io/component: controller 341 | app.kubernetes.io/instance: ingress-nginx 342 | app.kubernetes.io/name: ingress-nginx 343 | app.kubernetes.io/part-of: ingress-nginx 344 | app.kubernetes.io/version: 1.11.2 345 | name: ingress-nginx-controller 346 | namespace: ingress-nginx 347 | spec: 348 | ipFamilies: 349 | - IPv4 350 | ipFamilyPolicy: SingleStack 351 | ports: 352 | - appProtocol: http 353 | name: http 354 | port: 80 355 | protocol: TCP 356 | targetPort: http 357 | nodePort: 30080 358 | - appProtocol: https 359 | name: https 360 | port: 443 361 | protocol: TCP 362 | targetPort: https 363 | nodePort: 30443 364 | selector: 365 | app.kubernetes.io/component: controller 366 | app.kubernetes.io/instance: ingress-nginx 367 | app.kubernetes.io/name: ingress-nginx 368 | type: NodePort 369 | --- 370 | apiVersion: v1 371 | kind: Service 372 | metadata: 373 | labels: 374 | app.kubernetes.io/component: controller 375 | app.kubernetes.io/instance: ingress-nginx 376 | app.kubernetes.io/name: ingress-nginx 377 | app.kubernetes.io/part-of: ingress-nginx 378 | app.kubernetes.io/version: 1.11.2 379 | name: ingress-nginx-controller-admission 380 | namespace: ingress-nginx 381 | spec: 382 | ports: 383 | - appProtocol: https 384 | name: https-webhook 385 | port: 443 386 | targetPort: webhook 387 | selector: 388 | app.kubernetes.io/component: controller 389 | app.kubernetes.io/instance: ingress-nginx 390 | app.kubernetes.io/name: ingress-nginx 391 | type: ClusterIP 392 | --- 393 | apiVersion: apps/v1 394 | kind: Deployment 395 | metadata: 396 | labels: 397 | app.kubernetes.io/component: controller 398 | app.kubernetes.io/instance: ingress-nginx 399 | app.kubernetes.io/name: ingress-nginx 400 | app.kubernetes.io/part-of: ingress-nginx 401 | app.kubernetes.io/version: 1.11.2 402 | name: ingress-nginx-controller 403 | namespace: ingress-nginx 404 | spec: 405 | minReadySeconds: 0 406 | revisionHistoryLimit: 10 407 | selector: 408 | matchLabels: 409 | app.kubernetes.io/component: controller 410 | app.kubernetes.io/instance: ingress-nginx 411 | app.kubernetes.io/name: ingress-nginx 412 | strategy: 413 | rollingUpdate: 414 | maxUnavailable: 1 415 | type: RollingUpdate 416 | template: 417 | metadata: 418 | labels: 419 | app.kubernetes.io/component: controller 420 | app.kubernetes.io/instance: ingress-nginx 421 | app.kubernetes.io/name: ingress-nginx 422 | app.kubernetes.io/part-of: ingress-nginx 423 | app.kubernetes.io/version: 1.11.2 424 | spec: 425 | containers: 426 | - args: 427 | - /nginx-ingress-controller 428 | - --election-id=ingress-nginx-leader 429 | - --controller-class=k8s.io/ingress-nginx 430 | - --ingress-class=nginx 431 | - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller 432 | - --validating-webhook=:8443 433 | - --validating-webhook-certificate=/usr/local/certificates/cert 434 | - --validating-webhook-key=/usr/local/certificates/key 435 | - --enable-metrics=false 436 | env: 437 | - name: POD_NAME 438 | valueFrom: 439 | fieldRef: 440 | fieldPath: metadata.name 441 | - name: POD_NAMESPACE 442 | valueFrom: 443 | fieldRef: 444 | fieldPath: metadata.namespace 445 | - name: LD_PRELOAD 446 | value: /usr/local/lib/libmimalloc.so 447 | image: registry.k8s.io/ingress-nginx/controller:v1.11.2@sha256:d5f8217feeac4887cb1ed21f27c2674e58be06bd8f5184cacea2a69abaf78dce 448 | imagePullPolicy: IfNotPresent 449 | lifecycle: 450 | preStop: 451 | exec: 452 | command: 453 | - /wait-shutdown 454 | livenessProbe: 455 | failureThreshold: 5 456 | httpGet: 457 | path: /healthz 458 | port: 10254 459 | scheme: HTTP 460 | initialDelaySeconds: 10 461 | periodSeconds: 10 462 | successThreshold: 1 463 | timeoutSeconds: 1 464 | name: controller 465 | ports: 466 | - containerPort: 80 467 | name: http 468 | protocol: TCP 469 | - containerPort: 443 470 | name: https 471 | protocol: TCP 472 | - containerPort: 8443 473 | name: webhook 474 | protocol: TCP 475 | readinessProbe: 476 | failureThreshold: 3 477 | httpGet: 478 | path: /healthz 479 | port: 10254 480 | scheme: HTTP 481 | initialDelaySeconds: 10 482 | periodSeconds: 10 483 | successThreshold: 1 484 | timeoutSeconds: 1 485 | resources: 486 | requests: 487 | cpu: 100m 488 | memory: 90Mi 489 | securityContext: 490 | allowPrivilegeEscalation: false 491 | capabilities: 492 | add: 493 | - NET_BIND_SERVICE 494 | drop: 495 | - ALL 496 | readOnlyRootFilesystem: false 497 | runAsNonRoot: true 498 | runAsUser: 101 499 | seccompProfile: 500 | type: RuntimeDefault 501 | volumeMounts: 502 | - mountPath: /usr/local/certificates/ 503 | name: webhook-cert 504 | readOnly: true 505 | dnsPolicy: ClusterFirst 506 | nodeSelector: 507 | kubernetes.io/os: linux 508 | serviceAccountName: ingress-nginx 509 | terminationGracePeriodSeconds: 300 510 | volumes: 511 | - name: webhook-cert 512 | secret: 513 | secretName: ingress-nginx-admission 514 | --- 515 | apiVersion: batch/v1 516 | kind: Job 517 | metadata: 518 | labels: 519 | app.kubernetes.io/component: admission-webhook 520 | app.kubernetes.io/instance: ingress-nginx 521 | app.kubernetes.io/name: ingress-nginx 522 | app.kubernetes.io/part-of: ingress-nginx 523 | app.kubernetes.io/version: 1.11.2 524 | name: ingress-nginx-admission-create 525 | namespace: ingress-nginx 526 | spec: 527 | template: 528 | metadata: 529 | labels: 530 | app.kubernetes.io/component: admission-webhook 531 | app.kubernetes.io/instance: ingress-nginx 532 | app.kubernetes.io/name: ingress-nginx 533 | app.kubernetes.io/part-of: ingress-nginx 534 | app.kubernetes.io/version: 1.11.2 535 | name: ingress-nginx-admission-create 536 | spec: 537 | containers: 538 | - args: 539 | - create 540 | - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc 541 | - --namespace=$(POD_NAMESPACE) 542 | - --secret-name=ingress-nginx-admission 543 | env: 544 | - name: POD_NAMESPACE 545 | valueFrom: 546 | fieldRef: 547 | fieldPath: metadata.namespace 548 | image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.3@sha256:a320a50cc91bd15fd2d6fa6de58bd98c1bd64b9a6f926ce23a600d87043455a3 549 | imagePullPolicy: IfNotPresent 550 | name: create 551 | securityContext: 552 | allowPrivilegeEscalation: false 553 | capabilities: 554 | drop: 555 | - ALL 556 | readOnlyRootFilesystem: true 557 | runAsNonRoot: true 558 | runAsUser: 65532 559 | seccompProfile: 560 | type: RuntimeDefault 561 | nodeSelector: 562 | kubernetes.io/os: linux 563 | restartPolicy: OnFailure 564 | serviceAccountName: ingress-nginx-admission 565 | --- 566 | apiVersion: batch/v1 567 | kind: Job 568 | metadata: 569 | labels: 570 | app.kubernetes.io/component: admission-webhook 571 | app.kubernetes.io/instance: ingress-nginx 572 | app.kubernetes.io/name: ingress-nginx 573 | app.kubernetes.io/part-of: ingress-nginx 574 | app.kubernetes.io/version: 1.11.2 575 | name: ingress-nginx-admission-patch 576 | namespace: ingress-nginx 577 | spec: 578 | template: 579 | metadata: 580 | labels: 581 | app.kubernetes.io/component: admission-webhook 582 | app.kubernetes.io/instance: ingress-nginx 583 | app.kubernetes.io/name: ingress-nginx 584 | app.kubernetes.io/part-of: ingress-nginx 585 | app.kubernetes.io/version: 1.11.2 586 | name: ingress-nginx-admission-patch 587 | spec: 588 | containers: 589 | - args: 590 | - patch 591 | - --webhook-name=ingress-nginx-admission 592 | - --namespace=$(POD_NAMESPACE) 593 | - --patch-mutating=false 594 | - --secret-name=ingress-nginx-admission 595 | - --patch-failure-policy=Fail 596 | env: 597 | - name: POD_NAMESPACE 598 | valueFrom: 599 | fieldRef: 600 | fieldPath: metadata.namespace 601 | image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.3@sha256:a320a50cc91bd15fd2d6fa6de58bd98c1bd64b9a6f926ce23a600d87043455a3 602 | imagePullPolicy: IfNotPresent 603 | name: patch 604 | securityContext: 605 | allowPrivilegeEscalation: false 606 | capabilities: 607 | drop: 608 | - ALL 609 | readOnlyRootFilesystem: true 610 | runAsNonRoot: true 611 | runAsUser: 65532 612 | seccompProfile: 613 | type: RuntimeDefault 614 | nodeSelector: 615 | kubernetes.io/os: linux 616 | restartPolicy: OnFailure 617 | serviceAccountName: ingress-nginx-admission 618 | --- 619 | apiVersion: networking.k8s.io/v1 620 | kind: IngressClass 621 | metadata: 622 | labels: 623 | app.kubernetes.io/component: controller 624 | app.kubernetes.io/instance: ingress-nginx 625 | app.kubernetes.io/name: ingress-nginx 626 | app.kubernetes.io/part-of: ingress-nginx 627 | app.kubernetes.io/version: 1.11.2 628 | name: nginx 629 | spec: 630 | controller: k8s.io/ingress-nginx 631 | --- 632 | apiVersion: admissionregistration.k8s.io/v1 633 | kind: ValidatingWebhookConfiguration 634 | metadata: 635 | labels: 636 | app.kubernetes.io/component: admission-webhook 637 | app.kubernetes.io/instance: ingress-nginx 638 | app.kubernetes.io/name: ingress-nginx 639 | app.kubernetes.io/part-of: ingress-nginx 640 | app.kubernetes.io/version: 1.11.2 641 | name: ingress-nginx-admission 642 | webhooks: 643 | - admissionReviewVersions: 644 | - v1 645 | clientConfig: 646 | service: 647 | name: ingress-nginx-controller-admission 648 | namespace: ingress-nginx 649 | path: /networking/v1/ingresses 650 | failurePolicy: Fail 651 | matchPolicy: Equivalent 652 | name: validate.nginx.ingress.kubernetes.io 653 | rules: 654 | - apiGroups: 655 | - networking.k8s.io 656 | apiVersions: 657 | - v1 658 | operations: 659 | - CREATE 660 | - UPDATE 661 | resources: 662 | - ingresses 663 | sideEffects: None 664 | -------------------------------------------------------------------------------- /modules/k8s_provisioner/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_key_pair" "k8s_key" { 2 | key_name = var.key_name 3 | public_key = var.public_key 4 | } 5 | 6 | resource "aws_security_group" "this" { 7 | count = var.instance_count 8 | 9 | vpc_id = var.vpc_id 10 | 11 | dynamic "ingress" { 12 | for_each = var.security_group_rules 13 | content { 14 | from_port = ingress.value.from_port 15 | to_port = ingress.value.to_port 16 | protocol = ingress.value.protocol 17 | cidr_blocks = ingress.value.cidr_blocks 18 | } 19 | } 20 | 21 | dynamic "egress" { 22 | for_each = var.security_group_rules 23 | content { 24 | from_port = 0 25 | to_port = 0 26 | protocol = "-1" 27 | cidr_blocks = ["0.0.0.0/0"] 28 | } 29 | } 30 | 31 | tags = { 32 | Name = "PICK-K8S-SG-Instance-${count.index + 1}" 33 | } 34 | } 35 | 36 | resource "aws_subnet" "k8s_subnet" { 37 | vpc_id = var.vpc_id 38 | cidr_block = var.k8s_subnet_cidr 39 | availability_zone = var.k8s_subnet_az 40 | 41 | map_public_ip_on_launch = true 42 | 43 | tags = { 44 | Name = "PICK-K8S-Subnet" 45 | } 46 | } 47 | 48 | resource "aws_subnet" "k8s_subnet_2" { 49 | vpc_id = var.vpc_id 50 | cidr_block = var.k8s_subnet_cidr_2 51 | availability_zone = var.k8s_subnet_az_2 52 | 53 | map_public_ip_on_launch = true 54 | 55 | tags = { 56 | Name = "PICK-K8S-Subnet-2" 57 | } 58 | } 59 | 60 | resource "aws_instance" "control_plane" { 61 | ami = var.ami 62 | instance_type = var.cp_instance_type 63 | key_name = aws_key_pair.k8s_key.key_name 64 | subnet_id = aws_subnet.k8s_subnet.id 65 | 66 | root_block_device { 67 | volume_size = var.volume_size 68 | } 69 | 70 | vpc_security_group_ids = [aws_security_group.this[0].id] 71 | 72 | tags = { 73 | Name = "PICK-K8S-Control-Plane" 74 | } 75 | 76 | provisioner "remote-exec" { 77 | inline = [ 78 | <<-EOF 79 | cat < /dev/null 108 | sudo apt-get update && sudo apt-get install -y containerd.io 109 | sudo containerd config default | sudo tee /etc/containerd/config.toml 110 | sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml 111 | sudo sed -i 's/^KUBELET_EXTRA_ARGS=.*/KUBELET_EXTRA_ARGS=--max-pods=110/' /etc/default/kubelet 112 | sudo systemctl restart containerd 113 | sudo systemctl enable --now kubelet 114 | sudo kubeadm init --pod-network-cidr=10.10.0.0/16 --apiserver-advertise-address=${aws_instance.control_plane.private_ip} 115 | mkdir -p $HOME/.kube 116 | sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config 117 | sudo chown $(id -u):$(id -g) $HOME/.kube/config 118 | ################ INSTALAR AUTOCOMPLETE E ALIAS ###################### 119 | kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl > /dev/null 120 | echo 'alias k=kubectl' >>~/.bashrc 121 | echo 'complete -F __start_kubectl k' >>~/.bashrc 122 | ##################################################################### 123 | JOIN_COMMAND=$(kubeadm token create --print-join-command) 124 | aws ssm put-parameter --name "k8s_join_command" --value "$JOIN_COMMAND" --type "SecureString" --overwrite 125 | KUBECONFIG_FILE=$(cat ~/.kube/config) 126 | aws ssm put-parameter --name "k8s_kubeconfig" --value "$KUBECONFIG_FILE" --type "SecureString" --tier Advanced --overwrite 127 | #WeaveNet 128 | kubectl apply -f https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s.yaml 129 | EOF 130 | ] 131 | 132 | connection { 133 | type = "ssh" 134 | host = self.public_ip 135 | user = "ubuntu" 136 | private_key = var.private_key 137 | } 138 | } 139 | } 140 | 141 | resource "aws_lb" "k8s_alb" { 142 | name = "k8s-control-plane-alb" 143 | internal = false 144 | load_balancer_type = "application" 145 | security_groups = [aws_security_group.this[0].id] 146 | subnets = [aws_subnet.k8s_subnet.id, aws_subnet.k8s_subnet_2.id] 147 | 148 | enable_deletion_protection = false 149 | idle_timeout = 60 150 | 151 | tags = { 152 | Name = "PICK-K8S-Control-Plane-ALB" 153 | } 154 | } 155 | 156 | resource "aws_lb_target_group" "k8s_tg_https" { 157 | name = "k8s-control-plane-tg-https" 158 | port = 30443 159 | protocol = "HTTPS" 160 | vpc_id = var.vpc_id 161 | target_type = "instance" 162 | 163 | health_check { 164 | path = "/healthz" 165 | interval = 30 166 | timeout = 5 167 | healthy_threshold = 2 168 | unhealthy_threshold = 2 169 | matcher = "200" 170 | } 171 | } 172 | 173 | resource "aws_lb_target_group_attachment" "k8s_tg_attachment_https" { 174 | target_group_arn = aws_lb_target_group.k8s_tg_https.arn 175 | target_id = aws_instance.control_plane.id 176 | port = 30443 177 | } 178 | 179 | resource "aws_lb_listener" "k8s_listener_http" { 180 | load_balancer_arn = aws_lb.k8s_alb.arn 181 | port = 80 182 | protocol = "HTTP" 183 | 184 | default_action { 185 | type = "redirect" 186 | 187 | redirect { 188 | port = "443" 189 | protocol = "HTTPS" 190 | status_code = "HTTP_301" 191 | } 192 | } 193 | } 194 | 195 | resource "aws_lb_listener" "k8s_listener_https" { 196 | load_balancer_arn = aws_lb.k8s_alb.arn 197 | port = 443 198 | protocol = "HTTPS" 199 | ssl_policy = "ELBSecurityPolicy-2016-08" 200 | certificate_arn = "arn:aws:acm:us-east-1:381492273741:certificate/48066b99-9cc7-46f1-9e08-31aaeeb1a735" 201 | 202 | default_action { 203 | type = "forward" 204 | target_group_arn = aws_lb_target_group.k8s_tg_https.arn 205 | } 206 | } 207 | 208 | resource "aws_route53_record" "dns_records" { 209 | for_each = toset(var.dns_names) 210 | zone_id = "Z0250850H44ROGORV6C9" 211 | name = each.value 212 | type = "A" 213 | 214 | alias { 215 | name = aws_lb.k8s_alb.dns_name 216 | zone_id = aws_lb.k8s_alb.zone_id 217 | evaluate_target_health = true 218 | } 219 | 220 | lifecycle { 221 | create_before_destroy = true 222 | } 223 | } 224 | 225 | resource "aws_instance" "worker" { 226 | count = var.instance_count - 1 227 | ami = var.ami 228 | instance_type = var.instance_type 229 | key_name = aws_key_pair.k8s_key.key_name 230 | subnet_id = aws_subnet.k8s_subnet.id 231 | 232 | root_block_device { 233 | volume_size = var.volume_size 234 | } 235 | 236 | vpc_security_group_ids = [aws_security_group.this[count.index + 1].id] 237 | 238 | tags = { 239 | Name = "PICK-K8S-Worker-${count.index + 1}" 240 | } 241 | 242 | provisioner "remote-exec" { 243 | inline = [ 244 | <<-EOF 245 | cat < /dev/null 274 | sudo apt-get update && sudo apt-get install -y containerd.io 275 | sudo containerd config default | sudo tee /etc/containerd/config.toml 276 | sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml 277 | sudo systemctl restart containerd 278 | sudo systemctl enable --now kubelet 279 | ################ INSTALAR AUTOCOMPLETE E ALIAS ###################### 280 | kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl > /dev/null 281 | echo 'alias k=kubectl' >>~/.bashrc 282 | echo 'complete -F __start_kubectl k' >>~/.bashrc 283 | ##################################################################### 284 | JOIN_COMMAND=$(aws ssm get-parameter --name "k8s_join_command" --query "Parameter.Value" --with-decryption --output text) 285 | sudo $JOIN_COMMAND 286 | ################# INSTALANDO AS FERRAMENTAS QUE IREI UTILIZAR ####################### 287 | if [ "$(hostname)" = "PICK-worker-1" ]; then 288 | mkdir ~/.kube 289 | aws ssm get-parameter --name "k8s_kubeconfig" --query "Parameter.Value" --with-decryption --output text > ~/.kube/config 290 | sudo chmod 600 /home/ubuntu/.kube/config 291 | #Ingress Controller 292 | git clone https://github.com/FabioBartoli/LINUXtips-PICK.git 293 | kubectl apply --validate=false -f ./LINUXtips-PICK/modules/k8s_provisioner/ingress-deploy.yaml 294 | kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission 295 | # Helm 296 | curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 297 | chmod 700 get_helm.sh 298 | ./get_helm.sh 299 | # Adicionando repos ao Helm 300 | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts 301 | helm repo add harbor https://helm.goharbor.io 302 | helm repo add kyverno https://kyverno.github.io/kyverno/ 303 | helm repo add ingress https://fabiobartoli.github.io/LINUXtips-PICK/manifests/helm/ingress/ 304 | helm repo add giropops-app https://fabiobartoli.github.io/LINUXtips-PICK/manifests/helm/giropops-app/ 305 | helm repo add kedacore https://kedacore.github.io/charts 306 | helm repo update 307 | kubectl create ns harbor && kubectl create ns monitoring && kubectl create ns locust && kubectl create ns giropops 308 | fi 309 | EOF 310 | ] 311 | connection { 312 | type = "ssh" 313 | host = self.public_ip 314 | user = "ubuntu" 315 | private_key = var.private_key 316 | } 317 | } 318 | depends_on = [aws_instance.control_plane] 319 | } 320 | -------------------------------------------------------------------------------- /modules/k8s_provisioner/output.tf: -------------------------------------------------------------------------------- 1 | output "control_plane_public_ip" { 2 | value = aws_instance.control_plane.public_ip 3 | } 4 | 5 | output "worker_public_ips" { 6 | value = aws_instance.worker[*].public_ip 7 | } 8 | 9 | output "k8s_alb_dns_name" { 10 | description = "DNS name of the Kubernetes control plane ALB" 11 | value = aws_lb.k8s_alb.dns_name 12 | } -------------------------------------------------------------------------------- /modules/k8s_provisioner/variables.tf: -------------------------------------------------------------------------------- 1 | variable "ami" {} 2 | variable "cp_instance_type" {} 3 | variable "instance_type" {} 4 | variable "volume_size" {} 5 | variable "instance_count" {} 6 | variable "vpc_id" {} 7 | variable "k8s_subnet_cidr" {} 8 | variable "k8s_subnet_cidr_2" {} 9 | variable "k8s_subnet_az" {} 10 | variable "k8s_subnet_az_2" {} 11 | variable "AWS_ACCESS_KEY_ID" {} 12 | variable "AWS_SECRET_ACCESS_KEY" {} 13 | variable "private_key" {} 14 | variable "public_key" {} 15 | variable "security_group_rules" { 16 | type = list(object({ 17 | protocol = string 18 | from_port = number 19 | to_port = number 20 | cidr_blocks = list(string) 21 | })) 22 | } 23 | 24 | variable "key_name" { 25 | description = "The name of the key pair to use for the instances" 26 | default = "k8s-key" 27 | } 28 | 29 | variable "dns_names" { 30 | type = list(string) 31 | default = [ 32 | "giropops-senhas.fabiobartoli.com.br", 33 | "grafana.fabiobartoli.com.br", 34 | "harbor.fabiobartoli.com.br", 35 | "locust.fabiobartoli.com.br", 36 | "prometheus.fabiobartoli.com.br" 37 | ] 38 | } -------------------------------------------------------------------------------- /monitoring/grafana-alert.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": 1, 3 | "groups": [ 4 | { 5 | "orgId": 1, 6 | "name": "Evaluation", 7 | "folder": "Giropops-Alerts", 8 | "interval": "1m", 9 | "rules": [ 10 | { 11 | "uid": "ddw507ksjsfeoc", 12 | "title": "Giropops-Notify", 13 | "condition": "C", 14 | "data": [ 15 | { 16 | "refId": "A", 17 | "relativeTimeRange": { 18 | "from": 600, 19 | "to": 0 20 | }, 21 | "datasourceUid": "prometheus", 22 | "model": { 23 | "disableTextWrap": false, 24 | "editorMode": "builder", 25 | "expr": "kube_deployment_status_replicas{deployment=\"giropops-senhas\"}", 26 | "fullMetaSearch": false, 27 | "includeNullMetadata": true, 28 | "instant": true, 29 | "intervalMs": 1000, 30 | "legendFormat": "__auto", 31 | "maxDataPoints": 43200, 32 | "range": false, 33 | "refId": "A", 34 | "useBackend": false 35 | } 36 | }, 37 | { 38 | "refId": "B", 39 | "relativeTimeRange": { 40 | "from": 600, 41 | "to": 0 42 | }, 43 | "datasourceUid": "__expr__", 44 | "model": { 45 | "conditions": [ 46 | { 47 | "evaluator": { 48 | "params": [], 49 | "type": "gt" 50 | }, 51 | "operator": { 52 | "type": "and" 53 | }, 54 | "query": { 55 | "params": [ 56 | "B" 57 | ] 58 | }, 59 | "reducer": { 60 | "params": [], 61 | "type": "last" 62 | }, 63 | "type": "query" 64 | } 65 | ], 66 | "datasource": { 67 | "type": "__expr__", 68 | "uid": "__expr__" 69 | }, 70 | "expression": "A", 71 | "intervalMs": 1000, 72 | "maxDataPoints": 43200, 73 | "reducer": "last", 74 | "refId": "B", 75 | "type": "reduce" 76 | } 77 | }, 78 | { 79 | "refId": "C", 80 | "relativeTimeRange": { 81 | "from": 600, 82 | "to": 0 83 | }, 84 | "datasourceUid": "__expr__", 85 | "model": { 86 | "conditions": [ 87 | { 88 | "evaluator": { 89 | "params": [ 90 | 6 91 | ], 92 | "type": "gt" 93 | }, 94 | "operator": { 95 | "type": "and" 96 | }, 97 | "query": { 98 | "params": [ 99 | "C" 100 | ] 101 | }, 102 | "reducer": { 103 | "params": [], 104 | "type": "last" 105 | }, 106 | "type": "query" 107 | } 108 | ], 109 | "datasource": { 110 | "type": "__expr__", 111 | "uid": "__expr__" 112 | }, 113 | "expression": "B", 114 | "intervalMs": 1000, 115 | "maxDataPoints": 43200, 116 | "refId": "C", 117 | "type": "threshold" 118 | } 119 | } 120 | ], 121 | "noDataState": "NoData", 122 | "execErrState": "Error", 123 | "for": "1m", 124 | "annotations": { 125 | "summary": "Atenção! Número de deployments do giropops-senhas está alto!! ⚠️ ⚠️ ⚠️" 126 | }, 127 | "labels": {}, 128 | "isPaused": false, 129 | "notification_settings": { 130 | "receiver": "Fabio Bartoli" 131 | } 132 | } 133 | ] 134 | } 135 | ] 136 | } -------------------------------------------------------------------------------- /monitoring/prometheus-metrics.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: ServiceMonitor 3 | metadata: 4 | name: giropops-senhas-servicemonitor 5 | namespace: monitoring 6 | labels: 7 | release: kube-prometheus 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: giropops-senhas 12 | endpoints: 13 | - port: tcp-app 14 | path: /metrics 15 | interval: 30s 16 | namespaceSelector: 17 | matchNames: 18 | - giropops -------------------------------------------------------------------------------- /monitoring/proxy-prometheus.yaml: -------------------------------------------------------------------------------- 1 | # based on https://github.com/kubermatic/kubeone/issues/1215#issuecomment-992471229 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: metrics-proxy-config 6 | namespace: monitoring 7 | data: 8 | haproxy.cfg: | 9 | defaults 10 | mode http 11 | timeout connect 5000ms 12 | timeout client 5000ms 13 | timeout server 5000ms 14 | default-server maxconn 10 15 | 16 | frontend kube-controller-manager 17 | bind ${NODE_IP}:10257 18 | mode tcp 19 | default_backend kube-controller-manager 20 | 21 | backend kube-controller-manager 22 | mode tcp 23 | server kube-controller-manager 127.0.0.1:10257 24 | 25 | frontend kube-scheduler 26 | bind ${NODE_IP}:10259 27 | mode tcp 28 | default_backend kube-scheduler 29 | 30 | backend kube-scheduler 31 | mode tcp 32 | server kube-scheduler 127.0.0.1:10259 33 | 34 | frontend kube-proxy 35 | bind ${NODE_IP}:10249 36 | http-request deny if !{ path /metrics } 37 | default_backend kube-proxy 38 | 39 | backend kube-proxy 40 | server kube-proxy 127.0.0.1:10249 41 | 42 | frontend etcd 43 | bind ${NODE_IP}:2381 44 | http-request deny if !{ path /metrics } 45 | default_backend etcd 46 | 47 | backend etcd 48 | server etcd 127.0.0.1:2381 49 | --- 50 | apiVersion: apps/v1 51 | kind: DaemonSet 52 | metadata: 53 | name: metrics-proxy 54 | namespace: monitoring 55 | spec: 56 | selector: 57 | matchLabels: 58 | app: metrics-proxy 59 | template: 60 | metadata: 61 | labels: 62 | app: metrics-proxy 63 | spec: 64 | containers: 65 | - env: 66 | - name: NODE_IP 67 | valueFrom: 68 | fieldRef: 69 | apiVersion: v1 70 | fieldPath: status.hostIP 71 | image: docker.io/haproxy:2.5 72 | name: haproxy 73 | securityContext: 74 | allowPrivilegeEscalation: false 75 | runAsUser: 99 # 'haproxy' user 76 | volumeMounts: 77 | - mountPath: /usr/local/etc/haproxy 78 | name: config 79 | hostNetwork: true 80 | tolerations: 81 | - effect: NoSchedule 82 | key: node-role.kubernetes.io/master 83 | operator: Exists 84 | - effect: NoSchedule 85 | key: node-role.kubernetes.io/control-plane 86 | operator: Exists 87 | volumes: 88 | - configMap: 89 | name: metrics-proxy-config 90 | name: config -------------------------------------------------------------------------------- /scripts/install-helms.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Instalando o Harbor 4 | kubectl config set-context --current --namespace=harbor 5 | helm install harbor harbor/harbor --set expose.type=clusterIP --set expose.tls.auto.commonName=fabiobartoli.com.br \ 6 | --set persistence.enabled=false --set externalURL=https://harbor.fabiobartoli.com.br \ 7 | --set fullnameOverride=harbor-harbor --set trivy.enabled=true --namespace harbor 8 | # Instalando o Kube-Prometheus 9 | kubectl config set-context --current --namespace=monitoring 10 | helm install kube-prometheus prometheus-community/kube-prometheus-stack 11 | # Ajustando a captura de logs do Kubeadm 12 | kubectl apply -f /home/ubuntu/LINUXtips-PICK/monitoring/proxy-prometheus.yaml 13 | # Instalando o Kyverno 14 | kubectl config set-context --current --namespace=default 15 | helm install kyverno kyverno/kyverno --namespace kyverno --create-namespace 16 | # Criando Regras do Kyverno 17 | kubectl apply -f /home/ubuntu/LINUXtips-PICK/security/kyverno/ 18 | # Instalando o Locust 19 | sudo mkdir -p /usr/src/app/scripts/ 20 | kubectl apply -f /home/ubuntu/LINUXtips-PICK/manifests/locust/ 21 | # Passando a Secret de Login do Docker 22 | kubectl apply -f /home/ubuntu/LINUXtips-PICK/security/kyverno/docker-cred.yaml -n giropops 23 | kubectl apply -f /home/ubuntu/LINUXtips-PICK/security/kyverno/docker-cred.yaml -n kyverno 24 | # Instalando os Paths do Ingress 25 | helm install ingress-controller ingress/ingress-templates 26 | # Instalando o Metrics Server 27 | kubectl apply -f /home/ubuntu/LINUXtips-PICK/manifests/metrics-hpa/metrics-components.yaml 28 | # Instalando o Keda 29 | helm install keda kedacore/keda --namespace keda --create-namespace 30 | #helm install giropops giropops-app/giropops-chart --set env=stg 31 | #kubectl apply -f /home/ubuntu/LINUXtips-PICK/monitoring/prometheus-metrics.yaml 32 | #kubectl apply -f /home/ubuntu/LINUXtips-PICK/manifests/metrics-hpa/hpa-giropops.senhas.yaml 33 | #kubectl apply -f /home/ubuntu/LINUXtips-PICK/manifests/metrics-hpa/KEDA-scaledObjectRedis.yaml -------------------------------------------------------------------------------- /security/kyverno/allow-only-harbor-registry.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | name: ensure-images-from-trusted-registry 5 | namespace: giropops 6 | spec: 7 | validationFailureAction: Enforce 8 | rules: 9 | - name: trusted-registry 10 | match: 11 | resources: 12 | kinds: 13 | - Pod 14 | exclude: 15 | resources: 16 | namespaces: 17 | - harbor 18 | - ingress-nginx 19 | - kube-system 20 | - kyverno 21 | - monitoring 22 | - locust 23 | - keda 24 | validate: 25 | message: "Utilize imagens do Registry Harbor!" 26 | pattern: 27 | spec: 28 | containers: 29 | - name: "*" 30 | image: "harbor.fabiobartoli.com.br/*" -------------------------------------------------------------------------------- /security/kyverno/disallow-root-user.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | name: disallow-root-containers 5 | spec: 6 | validationFailureAction: Enforce 7 | rules: 8 | - name: disallow-root-user 9 | match: 10 | resources: 11 | kinds: 12 | - Pod 13 | exclude: 14 | resources: 15 | namespaces: 16 | - harbor 17 | - ingress-nginx 18 | - kube-system 19 | - kyverno 20 | - monitoring 21 | - keda 22 | validate: 23 | message: "Nenhum container pode ser executado como root! Ajuste as permissões" 24 | pattern: 25 | spec: 26 | containers: 27 | - securityContext: 28 | runAsUser: "!0" 29 | -------------------------------------------------------------------------------- /security/kyverno/disalow-default-ns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | name: disallow-default-namespace 5 | annotations: 6 | pod-policies.kyverno.io/autogen-controllers: none 7 | policies.kyverno.io/title: Disallow Default Namespace 8 | policies.kyverno.io/minversion: 1.6.0 9 | policies.kyverno.io/category: Multi-Tenancy 10 | policies.kyverno.io/severity: medium 11 | policies.kyverno.io/subject: Pod 12 | spec: 13 | validationFailureAction: Enforce 14 | failurePolicy: Fail 15 | background: true 16 | rules: 17 | - name: validate-namespace 18 | match: 19 | any: 20 | - resources: 21 | kinds: 22 | - Pod 23 | validate: 24 | message: "Não é possível utilizar o namespace default!" 25 | pattern: 26 | metadata: 27 | namespace: "!default" 28 | - name: validate-podcontroller-namespace 29 | match: 30 | any: 31 | - resources: 32 | kinds: 33 | - DaemonSet 34 | - Deployment 35 | - Job 36 | - StatefulSet 37 | validate: 38 | message: "Using 'default' namespace is not allowed for pod controllers." 39 | pattern: 40 | metadata: 41 | namespace: "!default" -------------------------------------------------------------------------------- /security/kyverno/docker-cred.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | .dockerconfigjson: ewoJImF1dGhzIjogewoJCSJoYXJib3IuZmFiaW9iYXJ0b2xpLmNvbS5iciI6IHsKCQkJImF1dGgiOiAiWm1GaWFXOWlZWEowYjJ4cE9sSndUbVpLTkZGRVpGVjBTbEJoYjFkaGRVZzFNMHBGTW5reVEycHUiCgkJfQoJfQp9 4 | kind: Secret 5 | metadata: 6 | name: regcred 7 | type: kubernetes.io/dockerconfigjson -------------------------------------------------------------------------------- /security/kyverno/harbor-signature.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | name: require-harbor-signature 5 | spec: 6 | validationFailureAction: Enforce 7 | background: false 8 | webhookTimeoutSeconds: 30 9 | failurePolicy: Fail 10 | rules: 11 | - name: require-harbor-signature 12 | match: 13 | any: 14 | - resources: 15 | kinds: 16 | - Pod 17 | - Deployment 18 | exclude: 19 | resources: 20 | namespaces: 21 | - harbor 22 | - ingress-nginx 23 | - kube-system 24 | - kyverno 25 | - monitoring 26 | - keda 27 | verifyImages: 28 | - imageReferences: 29 | - "harbor.fabiobartoli.com.br*" 30 | imageRegistryCredentials: 31 | secrets: 32 | - regcred 33 | attestors: 34 | - count: 1 35 | entries: 36 | - keys: 37 | publicKeys: |- 38 | -----BEGIN PUBLIC KEY----- 39 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjNhvsucfj6vfWeCfqGcXuFq01ewa 40 | +QhEAYBVkwg1IADM2CYIatTiEJiQDSNIeIiB9NUwfTJken9wkAMq8M4YxQ== 41 | -----END PUBLIC KEY----- 42 | rekor: 43 | ignoreTlog: true 44 | url: https://rekor.sigstore.dev -------------------------------------------------------------------------------- /security/kyverno/require-probes.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | name: require-pod-probes 5 | annotations: 6 | pod-policies.kyverno.io/autogen-controllers: DaemonSet,Deployment,StatefulSet 7 | policies.kyverno.io/title: Require Pod Probes 8 | policies.kyverno.io/category: Best Practices, EKS Best Practices 9 | policies.kyverno.io/severity: medium 10 | policies.kyverno.io/subject: Pod 11 | spec: 12 | validationFailureAction: Enforce 13 | failurePolicy: Fail 14 | background: true 15 | rules: 16 | - name: validate-probes 17 | match: 18 | any: 19 | - resources: 20 | kinds: 21 | - Pod 22 | exclude: 23 | any: 24 | - resources: 25 | namespaces: 26 | - harbor 27 | - ingress-nginx 28 | - kube-system 29 | - kyverno 30 | - monitoring 31 | - locust 32 | - keda 33 | preconditions: 34 | all: 35 | - key: "{{request.operation || 'BACKGROUND'}}" 36 | operator: AnyIn 37 | value: 38 | - CREATE 39 | - UPDATE 40 | validate: 41 | message: "Você deve incluir as Probies para fazer deploy!" 42 | foreach: 43 | - list: request.object.spec.containers[] 44 | deny: 45 | conditions: 46 | all: 47 | - key: livenessProbe 48 | operator: AllNotIn 49 | value: "{{ element.keys(@)[] }}" 50 | - key: startupProbe 51 | operator: AllNotIn 52 | value: "{{ element.keys(@)[] }}" 53 | - key: readinessProbe 54 | operator: AllNotIn 55 | value: "{{ element.keys(@)[] }}" -------------------------------------------------------------------------------- /security/kyverno/verify-sensitive-vars.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | name: disallow-sensitive-env-vars 5 | spec: 6 | validationFailureAction: Enforce 7 | failurePolicy: Fail 8 | rules: 9 | - name: disallow-sensitive-env-vars 10 | match: 11 | any: 12 | - resources: 13 | kinds: 14 | - Pod 15 | exclude: 16 | resources: 17 | namespaces: 18 | - harbor 19 | - ingress-nginx 20 | - kube-system 21 | - kyverno 22 | - monitoring 23 | - keda 24 | validate: 25 | message: "Secrets não devem ser montadas como variáveis!" 26 | pattern: 27 | spec: 28 | containers: 29 | - name: "*" 30 | =(env): 31 | - =(valueFrom): 32 | X(secretKeyRef): "null" 33 | - name: secrets-not-from-envfrom 34 | match: 35 | any: 36 | - resources: 37 | kinds: 38 | - Pod 39 | exclude: 40 | resources: 41 | namespaces: 42 | - harbor 43 | - ingress-nginx 44 | - kube-system 45 | - kyverno 46 | - monitoring 47 | validate: 48 | message: "Secrets não devem ser montadas como variáveis!" 49 | pattern: 50 | spec: 51 | containers: 52 | - name: "*" 53 | =(envFrom): 54 | - X(secretRef): "null" 55 | -------------------------------------------------------------------------------- /security/melange/apko.yaml: -------------------------------------------------------------------------------- 1 | contents: 2 | repositories: 3 | - https://dl-cdn.alpinelinux.org/alpine/edge/main 4 | - /work/packages 5 | packages: 6 | - alpine-baselayout 7 | - giropops-senhas 8 | - curl 9 | accounts: 10 | groups: 11 | - groupname: nonroot 12 | gid: 65532 13 | users: 14 | - username: nonroot 15 | uid: 65532 16 | gid: 65532 17 | run-as: 65532 18 | environment: 19 | FLASK_APP: "/usr/bin/giropops-senhas" 20 | entrypoint: 21 | command: /usr/bin/giropops-senhas 22 | -------------------------------------------------------------------------------- /security/melange/melange.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: giropops-senhas 3 | version: 0.1 4 | description: Password Generator by LinuxTips 5 | dependencies: 6 | runtime: 7 | - python3 8 | 9 | environment: 10 | contents: 11 | keyring: 12 | - ./melange.rsa.pub 13 | repositories: 14 | - https://dl-cdn.alpinelinux.org/alpine/edge/main 15 | - https://dl-cdn.alpinelinux.org/alpine/edge/community 16 | packages: 17 | - alpine-baselayout-data 18 | - ca-certificates-bundle 19 | - busybox 20 | - gcc 21 | - musl-dev 22 | - python3 23 | - python3-dev 24 | - py3-pip 25 | - py3-virtualenv 26 | pipeline: 27 | - name: Build Python application 28 | runs: | 29 | EXECDIR="${{targets.destdir}}/usr/bin" 30 | WEBAPPDIR="${{targets.destdir}}/usr/share/webapps/giropops-senhas" 31 | mkdir -p "${EXECDIR}" "${WEBAPPDIR}" 32 | echo "#!/usr/share/webapps/giropops-senhas/venv/bin/python3" > "${EXECDIR}/giropops-senhas" 33 | cat app.py >> "${EXECDIR}/giropops-senhas" 34 | chmod +x "${EXECDIR}/giropops-senhas" 35 | virtualenv "${WEBAPPDIR}/venv" 36 | cp -r templates/ static/ ${WEBAPPDIR}/ 37 | sh -c "source '${WEBAPPDIR}/venv/bin/activate' && pip install -r requirements.txt" --------------------------------------------------------------------------------