Terraform — AWS SSM: Извлечь содержимое

В SSM Parameter Store содержится JSON следующего вида:

{
  "username": "admin",
  "password": "password"
}

 

Необходимо извлечь логин и пароль, и использовать их значения в Terraform коде. Для этого можно воспользоваться следующей конструкцией:

# Should be there before the apply
data "aws_ssm_parameter" "rds-admin-user" {
  name  = "/ARTEM-SERVICES/PROD/RDS/CREDENTIALS"
}

locals {
  additional_rds_username      = jsondecode(data.aws_ssm_parameter.rds-admin-user.value)["username"]
  additional_rds_user_password = jsondecode(data.aws_ssm_parameter.rds-admin-user.value)["password"]
}

 

И использовать переменные:

local.additional_rds_username
local.additional_rds_user_password

OpenVPN — Исключить определенные IP или сети из маршрутов

Для того, чтобы исключить определенный диапазон или IP адрес необходимо добавить параметр "net_gateway".

К примеру, необходимо чтобы сеть "10.0.0.0/8" маршрутизировалась через VPN, но при этом исключить из маршрута сеть "10.0.1.0/24" запись в конфигурационном файле будет выглядеть следующим образом:

push "route 10.0.0.0 255.0.0.0"
push "route 10.0.1.0 255.255.255.0 net_gateway"

Linux — Конвертировать приватный SSH ключ формата OpenSSH в PEM формат

Для того, чтобы конвертировать существующий приватный ключ в PEM формат, достаточно выполнить следующее:

ssh-keygen -p -N "" -m pem -f /path/to/key

FIX ERROR — Centos: Public key for jenkins-2.289.3-1.1.noarch.rpm is not installed

При попытке обновить "Jenkins" на CentOS 7 может возникнуть следующая ошибка:

Public key for jenkins-2.289.3-1.1.noarch.rpm is not installed

 

Для того чтобы ее устранить достаточно импортировать ключ репозитория:

sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key

 

FIX ERROR — Ubuntu: /etc/resolv.conf is not a symbolic link to /run/resolvconf/resolv.conf

Подобная ошибка:

/etc/resolvconf/update.d/libc: Warning: /etc/resolv.conf is not a symbolic link to /run/resolvconf/resolv.conf

 

Может свидетельствовать о том, что символическая ссылка "/etc/resolv.conf" отсутствует, для того чтобы это исправить можно как создать ее вручную:

sudo ln -s /run/resolvconf/resolv.conf /etc/resolv.conf
sudo resolvconf -u

 

Или же запустить переконфигурацию "resolvconf" используя следующую команду:

sudo dpkg-reconfigure resolvconf

Jenkins — Python VirtualEnv с выбором версий

 

Для выбора версии питона в пайплайне, нужно чтобы нужные версии были установлены в системе.

Дальнейшие действия были выполнены на CentOS 7 и установка бинарников происходила в директорию "/usr/bin/" для удобства, так как в системе уже установлены версии "2.7" и "3.6" из репозитория по данному пути.

Устанавливаем зависимости:

yum install gcc openssl-devel bzip2-devel libffi-devel wget

 

Скачиваем нужные исходники нужных версий, в данном случае: "3.7", "3.8" и "3.9"

cd /usr/src

wget https://www.python.org/ftp/python/3.7.9/Python-3.7.9.tgz
wget https://www.python.org/ftp/python/3.8.9/Python-3.8.9.tgz
wget https://www.python.org/ftp/python/3.9.5/Python-3.9.5.tgz

 

Разархивируем:

tar xzf Python-3.7.9.tgz
tar xzf Python-3.8.9.tgz
tar xzf Python-3.9.5.tgz

 

Устанавливаем:

cd Python-3.7.9
./configure --enable-optimizations --prefix=/usr
make altinstall

cd ../Python-3.8.9
./configure --enable-optimizations --prefix=/usr
make altinstall

cd ../Python-3.9.5
./configure --enable-optimizations --prefix=/usr
make altinstall

 

Теперь установим плагин Pyenv Pipeline

Переходим в настройки Jenkins

 

Раздел "Управление плагинами"

 

Переходим к вкладке "Доступные" и в поиске указываем "Pyenv Pipeline"

Устанавливаем его.

 

Для выбора версии будем использовать параметр "choice"

Pipeline:

properties([
  parameters([
    choice(
      name: 'PYTHON',
      description: 'Choose Python version',
      choices: ["python2.7", "python3.6", "python3.7", "python3.8", "python3.9"].join("\n")
    ),
    base64File(
      name: 'REQUIREMENTS_FILE',
      description: 'Upload requirements file (Optional)'
    )
  ])
])

pipeline {
  agent any
  options {
    buildDiscarder(logRotator(numToKeepStr: '5'))
    timeout(time: 60, unit:'MINUTES')
    timestamps()
  }
  stages {
    stage("Python"){
      steps{
        withPythonEnv("/usr/bin/${params.PYTHON}") {
          script {
            if ( env.REQUIREMENTS_FILE.isEmpty() ) {
              sh "python --version"
              sh "pip --version"
              sh "echo Requirements file not set. Run Python without requirements file."
            }
            else {
              sh "python --version"
              sh "pip --version"
              sh "echo Requirements file found. Run PIP install using requirements file."
              withFileParameter('REQUIREMENTS_FILE') {
                sh 'cat $REQUIREMENTS_FILE > requirements.txt'
              }
              sh "pip install -r requirements.txt"
            }
          }
        }
      }
    }
  }
}

 

Запускаем сборку:

Выберем нужную версию, к примеру "3.9" и запустим сборку:

Проверим лог сборки:

AWS — S3: Разрешить публичный доступ к объектам через VPN

Цель:

Разрешить публичный доступ на чтение для всех объектов в S3 корзине только используя VPN подключение, для подключения с мира объекты должны быть не публичными. В качестве сервиса VPN используется OpenVPN, который может быть развернут где угодно, поэтому разрешающие правило будем строить на проверки IP адреса.

 

Для начала нужно узнать список сетей, которые относятся к эндпоинтам S3 сервиса в нужном нам регионе, чтобы не заворачивать весь траффик через VPN. Для это скачиваем актуальный список сетей и парсим его:

jq '.prefixes[] | select(.region=="eu-central-1") | select(.service=="S3") | .ip_prefix' < ip-ranges.json

 

Где, "eu-central-1" регион, в котором находится нужная S3 корзина.

 

Вы должны получить результат вида:

"52.219.170.0/23"
"52.219.168.0/24"
"3.5.136.0/22"
"52.219.72.0/22"
"52.219.44.0/22"
"52.219.169.0/24"
"52.219.140.0/24"
"54.231.192.0/20"
"3.5.134.0/23"
"3.65.246.0/28"
"3.65.246.16/28"

 

Теперь переводим маску подсети в 4-х байтный формат и добавляем в конфигурацию OpenVPN сервера, как "push" параметры:

push "route 52.219.170.0 255.255.254.0"
push "route 52.219.168.0 255.255.255.0"
push "route 3.5.136.0 255.255.252.0"
push "route 52.219.72.0 255.255.252.0"
push "route 52.219.44.0 255.255.252.0"
push "route 52.219.169.0 255.255.255.0"
push "route 52.219.140.0 255.255.255.0"
push "route 54.231.192.0 255.255.240.0"
push "route 3.5.134.0 255.255.254.0"
push "route 3.65.246.0 255.255.255.240"
push "route 3.65.246.16 255.255.255.240"

 

Перезапускаем сервис OpenVPN сервера и после переподключения мы должны получить список необходимых сетей и траффик будет идти через VPN подключение.

 

Теперь осталось добавить следующую политику к S3 корзине:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Allow only from VPN",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::artem-services",
                "arn:aws:s3:::artem-services/*"
            ],
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": "1.2.3.4"
                }
            }
        }
    ]
}

 

Где, "artem-services" — имя S3 корзины, а "1.2.3.4" — IP адрес OpenVPN сервера.

Python — AWS EBS создание снапшотов на основе тега и хранение только одной последней версии

Данный скрипт ищет EBS в регионе "eu-west-1" с тегом, ключ которого "Application" и значение передается как аргумент, создает снапшот данного EBS. Так же по тому же принципу он ищет снапшот по тегу, и удаляет все, кроме последнего.

Пример запуска для создания снапшота:

python3 main.py create artem-test-app

 

Пример запуска для удаления старых снапшотов:

python3 main.py cleanup artem-test-app

 

main.py:

from __future__ import print_function
from datetime import datetime
import sys
import boto3
import botocore
import urllib.request

ec2 = boto3.client('ec2', region_name='eu-west-1')

def create_snapshot(app):
    print("Creating snapshot for " + str(app))
    # Get Instance ID
    instances_id = []
    response = ec2.describe_instances(
        Filters = [
            {
                'Name': 'tag:Application',
                'Values': [
                    app,
                ]
            },
            {
                'Name': 'instance-state-name',
                'Values': ['running']
            }
        ]
    )

    for reservation in response['Reservations']:
        for instance in reservation['Instances']:
            instances_id.append(instance['InstanceId'])

    # Get Volumes ID
    volumes_id = []
    for instance_id in instances_id:
        response = ec2.describe_instance_attribute(InstanceId = instance_id, Attribute = 'blockDeviceMapping')
        for BlockDeviceMappings in response['BlockDeviceMappings']:
            volumes_id.append(BlockDeviceMappings['Ebs']['VolumeId'])

    # Create Volume Snapshot
    for volume_id in volumes_id:
        date = datetime.now()
        response = ec2.create_snapshot(
            Description = app + " snapshot created by Jenkins at " + str(date),
            VolumeId = volume_id,
            TagSpecifications=[
                {
                    'ResourceType': 'snapshot',
                    'Tags': [
                        {
                            'Key': 'Name',
                            'Value': app + "-snapshot"
                        },
                    ]
                },
            ]
        )


        try:
            print("Waiting until snapshot will be created...")
            snapshot_id = response['SnapshotId']
            snapshot_complete_waiter = ec2.get_waiter('snapshot_completed')
            snapshot_complete_waiter.wait(SnapshotIds=[snapshot_id], WaiterConfig = { 'Delay': 30, 'MaxAttempts': 120})

        except botocore.exceptions.WaiterError as e:
                print(e)

def cleanup_snapshot(app):
    print("Clean up step.")
    print("Looking for all snapshots regarding " + str(app) + " application.")
    # Get snapshots
    response = ec2.describe_snapshots(
        Filters = [
            {
                'Name': 'tag:Name',
                'Values': [
                    app + "-snapshot",
                ]
            },
        ],
        OwnerIds = [
            'self',
        ]
    )

    sorted_snapshots = sorted(response['Snapshots'], key=lambda k: k['StartTime'])

    # Clean up snapshots and keep only the latest snapshot
    for snapshot in sorted_snapshots[:-1]:
        print("Deleting snapshot: " + str(snapshot['SnapshotId']))
        response = ec2.delete_snapshot(
            SnapshotId = snapshot['SnapshotId']
        )

def main():
    if sys.argv[1:] and sys.argv[2:]:
        action = sys.argv[1]
        app = sys.argv[2]
        if action == 'create':
            create_snapshot(app)
        elif action == 'cleanup':
            cleanup_snapshot(app)
        else:
            print("Wrong action: " + str(action))
    else:
        print("This script for create and cleanup snapshots")
        print("Usage  : python3 " + sys.argv[0] + " {create|cleanup} " + "{application_tag}")
        print("Example: python3 " + sys.argv[0] + " create " + "ebs-snapshot-check")

if __name__ == '__main__':
    main()

 

 

Jenkins — Active Choice: SSH — Вернуть значения удаленной команды

Для параметризованной сборки, понадобится плагин Active Choices

Переходим в настройки Jenkins

 

Раздел "Управление плагинами"

 

Переходим к вкладке "Доступные" и в поиске указываем "Active Choices"

Устанавливаем его.

 

Создаем "New Item" — "Pipeline", указываем, что это будет параметризованная сборка, и добавляем параметр "Active Choices Parameter"

 

 

 

Указываем, что это "Groovy Script" и вставляем туда следующее:

import com.cloudbees.plugins.credentials.Credentials;
import com.cloudbees.plugins.credentials.CredentialsNameProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.ChannelExec;
import jenkins.model.*


sshCredentialsId = 'instance_ssh_key'
sshUser = 'artem'
sshInstance = '192.168.1.100'

def sshCreds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(SSHUserPrivateKey.class, Jenkins.instance, null, null ).find({it.id == sshCredentialsId});

String ssh_key_data = sshCreds.getPrivateKeys()

JSch jsch = new JSch();
jsch.addIdentity("id_rsa", ssh_key_data.getBytes(), null, null);
Session session = jsch.getSession(sshUser, sshInstance, 22);
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "no");
session.setConfig(prop);

session.connect();

ChannelExec channelssh = (ChannelExec)session.openChannel("exec");
channelssh.setCommand("cat /home/artem/secret_list");
channelssh.connect();

def result = []
InputStream is=channelssh.getInputStream();
is.eachLine {
    result.add(it)
}
channelssh.disconnect();

return result

 

Где значение переменных, "sshCredentialsId" — Jenkins Credentials ID с типом: "SSH Username with private key";

"sshUser" — имя пользователя для SSH подключения;

"sshInstance" — IP адрес или доменное имя удаленного инстанса;

"cat /home/artem/secret_list" — команда, результат которой мы хотим вернуть

 

Jenkins — Active Choice: S3 — Вернуть нужное значение ключа с JSON файла

Для параметризованной сборки, понадобится плагин Active Choices

Переходим в настройки Jenkins

 

Раздел "Управление плагинами"

 

Переходим к вкладке "Доступные" и в поиске указываем "Active Choices"

Устанавливаем его.

 

Создаем "New Item" — "Pipeline", указываем, что это будет параметризованная сборка, и добавляем параметр "Active Choices Parameter"

 

 

 

Указываем, что это "Groovy Script" и вставляем туда следующее:

import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.S3Object;
import com.cloudbees.plugins.credentials.Credentials;
import com.cloudbees.plugins.credentials.CredentialsNameProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.jenkins.plugins.awscredentials.AmazonWebServicesCredentials;
import groovy.json.JsonSlurper;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import jenkins.model.*
  
def AWS_REGION = 'eu-west-1'
def BUCKET_NAME = 'artem.services'
def INFO_FILE = 'info.json'

credentialsId = 'aws_credentials'

def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(AmazonWebServicesCredentials.class, Jenkins.instance, null, null ).find({it.id == credentialsId});


AmazonS3 s3Client = AmazonS3ClientBuilder.standard().withCredentials(creds).withRegion(AWS_REGION).build(); 

S3Object s3Object = s3Client.getObject(BUCKET_NAME, INFO_FILE)

try {
    BufferedReader reader = new BufferedReader(new InputStreamReader(s3Object.getObjectContent()))
    s3Data = reader.lines().collect(Collectors.joining("\n"));
} catch (Exception e) {
    System.err.println(e.getMessage());
    System.exit(1);
}

def json = new JsonSlurper().parseText(s3Data)

return json.instance.ip

 

Где значение переменных, "credentialsId" — Jenkins Credentials ID с AWS Access Key и AWS Secret Key (если для Jenkins инстанса не используется IAM Role, или он развернут не в AWS Cloud);

"AWS_REGION" — AWS регион, где находится S3 Bucket;

"BUCKET_NAME" — имя S3 бакета;

"INFO_FILE" — ключ объекта в корзине, в нашем случае JSON файла;

"return json.instance.ip" — возвращаем значение ключа instance: {'ip': "1.2.3.4}