Показаны сообщения с ярлыком aws. Показать все сообщения
Показаны сообщения с ярлыком aws. Показать все сообщения

вторник, 23 ноября 2021 г.

AWS S3 bucket: Access Denied for Administrator

 Ситуация неприятная. Пользователь крутил политики на бакете и получил ситуацию, когда доступ к бакету закончился для него абсолютно. Он попросил меня удалить бакет но .... моих прав администратора оказалось недостаточно. Ни через Веб консоль ни через CLI - никак!

Нельзя было ни удалить бакет, ни снять политику, не прочитать бакет. В консоле все до единого пункты управления имели одно и то же - Access Denied.

Ситуацию разрешил доступ Root юзера в консоль. У него есть доступ к бакетам вне зависимости от Bucket Policy. Оказалось, что пользователь установил bucket policy c правилом доступа только из подсетей офиса, и это были не маршрутизируемые адреса :))


пятница, 23 августа 2019 г.

Terraform. Creating dynamic VPC

Задача - создавать выделенные окружения ECS для каждого приложения. Выделенные имеется ввиду что каждое приложение это полностью свою структура на базе выделенной VPC и иже с ней. И что самое забавное, адреса VPC должны быть уникальны, то есть не допускается перекрытие VPC CIDR.

Не сложно, казалось бы сделать первую часть, просто используя terraform workspace. А вот со второй задачей было интересно.

Алгоритм я кодил такой - делаем запрос в регион по тэгам на наличие каких-то уже живых VPC и если они есть - получаю их cidr_block. Рядом делаю массив с помощью утилитки prips, который содержит все возможные VPC CIDR для моих приложений. далее удаляю из этого массива со всеми возможными CIDR те, что уже заняты (созданные ранее, живые) и первый минимальный CIDR - мой кандидат. Чуть более подробнее:
у меня выбрался один большой блок для DEV-env = 10.69.64.0/19
для экономии адресов было решено, что достаточно будет иметь для каждого приложения VPC CIDR класса /26, что дает возможность (AWS тут ограничивает минимальным /28) создать до 4-х подсетей в такой vpc класса /28), а это либо пара пар private-public, либо до 4-х private или public subnets. так же этот алгоритм позволяет переиспользовать освободившиеся VPC CIDR а не расти вверх до упора.

Идеи под реализации были такие:
1. использовать null_resource + local_file. То есть вызывается null_resource, который запускает шел-код, которому передаются переменные окружения из текущего состояния Terraform (CIDR, WORKSPACE и так далее). далее на выходе этого скрипта - первый свободный CIDR из нашего большого 10.69.64.0/19. И все было бы хорошо, но я не смог переубедить терраформ не убивать каждый раз то что он создал. Это потому что null_resource + local_file - это динамические объекты и они каждый раз при новом запуске не известны. Ни какие триггеры не помогли мне в этом.

вариант 2 оказался рабочий и даже мне понравился больше
2. Я нашел, что в 12-м терраформе есть data-source тип external (https://www.terraform.io/docs/providers/external/data_source.html). Вот на его базе это и получилось реализовать так как мне нужно было для решения задачи. Я покажу ключевые элементы что бы было понятно:
variable.tf:
-------------------------------------
data "external" "app-cidr" {
  program = ["${path.module}/getFirstFreeVpcCidr.sh"]
  query = {
    PRIPS         = "${path.module}/prips"
    VPC_CIDR_FULL = "${lookup(var.vpc-cidr, var.env)}"
    APP_CIDR_MASK = "${lookup(var.app-vpc-mask, var.env)}"
    REGION        = "${var.region}"
    STAGE_ENV     = "${var.env}"
    WORKSPACE_NAME = terraform.workspace
  }
}
--------------------------------------
здесь я указываю где лежит скрипт, который реализует логику. Тут особенность этого data-source в том, что ему на вход нужен JSON и выдавать он должен JSON. Переменные окружения передаем в блоке query {} а в скрипте эти переменные придется преобразовать
Вот весь скрипт:
-------------------------
#!/bin/bash
set -e
eval "$(jq -r '@sh "PRIPS=\(.PRIPS) VPC_CIDR_FULL=\(.VPC_CIDR_FULL) APP_CIDR_MASK=\(.APP_CIDR_MASK) REGION=\(.REGION) STAGE_ENV=\(.STAGE_ENV) WORKSPACE_NAME=\(.WORKSPACE_NAME)"')"

[[ -z "${PRIPS}" ]] && exit 1
[[ -z "${VPC_CIDR_FULL}" ]] && exit 1
[[ -z "${APP_CIDR_MASK}" ]] && exit 1
[[ -z "${REGION}" ]] && exit 1
[[ -z "${STAGE_ENV}" ]] && exit 1

declare -a VPC_CIDR_CURR
## check for exists vpc. If exists return current CIDR-block
VPC_CIDR_CURR=($(aws ec2 describe-vpcs --filters "Name=tag-key,Values=Environment" "Name=tag-value,Values=${WORKSPACE_NAME}-vpc" --region=${REGION} --query "Vpcs[*].CidrBlock" --output text))

if [ -z ${VPC_CIDR_CURR} ]; then

  VPC_CIDR_CURR=($(aws ec2 describe-vpcs --filters "Name=tag-key,Values=Environment" "Name=tag-value,Values=*-${STAGE_ENV}-vpc" --region=${REGION} --query "Vpcs[*].CidrBlock" --output text))

  #for test purpose: add element to array
  #VPC_CIDR_CURR=("${VPC_CIDR_CURR[@]}" "10.69.64.0/27")

  declare -a ALL_AVAIL_SUBNETS
  ### prips -i
  ### The offset is a value of adresses into app-vpc
  APP_CIDR="$( echo ${VPC_CIDR_FULL}|cut -d/ -f-1 )${APP_CIDR_MASK}"
  COUNT_IPS_INTO_APP_CIDR=$( ${PRIPS} ${APP_CIDR}|wc -l )
  #echo "Count - $COUNT_IPS_INTO_APP_CIDR"
  ALL_AVAIL_SUBNETS=($( ${PRIPS} -i ${COUNT_IPS_INTO_APP_CIDR} ${VPC_CIDR_FULL} ))

  for i in ${VPC_CIDR_CURR[@]}
  do
    l=$(echo $i|cut -d/ -f-1)
    ALL_AVAIL_SUBNETS=( ${ALL_AVAIL_SUBNETS[@]/$l/} )
  done

  FIRST_FREE_CIDR=${ALL_AVAIL_SUBNETS[@]:0:1}
  APP_CIDR="${FIRST_FREE_CIDR}${APP_CIDR_MASK}"
else
  APP_CIDR=${VPC_CIDR_CURR}
fi

echo "${APP_CIDR}" >> $LOG
jq -n --arg APP_CIDR "${APP_CIDR}" '{"app-cidr":$APP_CIDR}'
---------------------------------

Здесь я получаю переменные окружения из query{} блока. Преобразую их в башевские для работы. далее проверяю, есть ли уже vpc с таким именем как текущий workspace. Если есть, то выдаю его CIDR. Именно это не дает терраформу убить то что было создано уже.
Если такой vpc еще нет, то вычисляем первый свободный блок и отдаем его JSON-м.

теперь терраформ знает нужный ему CIDR. У меня модульный сценарий и есть другие, заранее оговоренные CIDR для предопределенных окружений (dev/stage/prod). Что бы оставить их рабочими я добавляю наш только что вычисленный CIDR в переменную типа MAP:

variable "vpc-cidr" {
  type    = map
  default = {
    dev              = "10.69.0.0/19"
    staging          = "10.69.64.0/19"
    prod             = "10.69.128.0/19"
 }
}


locals {
  vpc-cidr = merge(var.vpc-cidr,{"${terraform.workspace}" = "${data.external.app-cidr.result.app-cidr}"})
  vpcCidr = lookup(local.vpc-cidr, terraform.workspace)
}

## UPDATE after release 0.12.7 (fixed lookup error)
locals {
  vpcCidr = lookup(var.vpc-cidr, terraform.workspace, data.external.app-cidr.result.app-cidr)
}

и далее в модуле уже применяю как обычно:
resource "aws_vpc" "vpc" {
  cidr_block           = local.vpcCidr
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name        = "${terraform.workspace}-vpc"
    Environment = terraform.workspace
  }
}

среда, 5 июня 2019 г.

AWS. IAM policy for start/stop instance by tag

Задача была простой - экономить деньги и включать-выключать машинку при необходимости.
Человек умел работать из aws cli. Ему в итоге дадены были права на start/stop/describe. А вот указать конкретный InstanceID не можно и потому вот такой Condition по тэгу Role: jupyter-anaconda3

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "jupyter1",
            "Effect": "Allow",
            "Action": [
                "ec2:StartInstances",
                "ec2:DescribeInstances",
                "ec2:StopInstances"
            ],
            "Resource": "arn:aws:ec2:us-east-2:*:instance/*",
            "Condition": {
                "StringEquals": {
                    "ec2:ResourceTag/Role": "jupyter-anaconda3"
                }
            }
        },
        {
            "Sid": "jupyter2",
            "Effect": "Allow",
            "Action": [
                "ec2:Describe*",
                "cloudwatch:*"
            ],
            "Resource": "*"
        }
    ]
}

Конечно, не забыть о том, что бы не давать человеку права на создание или смену тэгов всем другим инстанцам а то смысла не будет в этом полиси....

среда, 8 мая 2019 г.

AWS CLI and JumpCloud

JumpCloud - удобно, на 10 пользователей бесплатно. Удобно, когда у тебя много AWS аккаунтов, есть небольшая команда и хочется сделать удобно и безопаснее.
Как создавать в JC пользователей и прочая интеграция с AWS есть на сайте в доках.
Тут у меня встал вопрос как работать с командной строчки в таком варианте, когда есть SSO JempCloud + MFA, нет пользователя и нет ключей.
Зато есть saml2aws. Штука вполне себе выполняет задачу: saml2aws configure -a PROFILE_NAME и saml2aws login
После чего появляется .aws/credentials. Далее можно как привычно export AWS_PROFILE=PROFILE_NAME или каждой команде aws --profile PROFILE_NAME

понедельник, 18 марта 2019 г.

AWS. Схема создания уникальных имен (тэгов) в ASG.

Не первый раз встречается задача присвоения уникальных имен или идентификаторов для ASG. Ну вот надо и всё :) Задача на первый вгляд кажется простая, чего бы там не поставить в UserData вызов aws ec2 create-tags .... тут и начинается задача. Тэг уникальный должен быть а значит надо как то вычислять его уникальность. Решений в лоб несколько приходит на ум.
Например, читать все тэги что уже есть у группы, сортировать, найти максимальный, прибавить 1 и получить решение задачи. Проблемы сразу вылезают:
 - нумерация будет со временем большими цифрами, особенно если группа часто изменяется по составу.
- вторая более серьезная и будет касаться почти всех остальных решений в лоб - тэги на инстанцах появляются не сразу же, может пройти какое-то время прежде чем тэг появится. А это означает, что при старте более одного нового инстанца в группе почти гарантированно каждый новый инстанц получит один и тот же номер.

Что я придумал делать, что бы избавиться от этих двух проблем.
Решение выглядит так - инстанц стартует с ролью, которой разрешено писать сообщения в SQS. В Userdata простой скрипт:
-----------------------
#!/bin/bash
echo "Send to SQS out INSTANCE ID"

INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
AVZONE=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone);
REGION=${AVZONE::-1}

echo "INSTANCE_ID: ${INSTANCE_ID}"
sqsStatus="aws sqs send-message --queue-url https://sqs.${REGION}.amazonaws.com/XXXXXXXXX/BName --message-body \"${INSTANCE_ID}\" --delay-seconds 10 --region ${REGION}"

if [ "$(eval $sqsStatus)" ]; then
 echo "Message sent"
else
 echo "Error sending message to SQS"
 exit 1
fi
-------------------------
Скрипт просто отправляет в SQS сообщение, в теле которого содержится InstanceID

SQS Standard. Я не смог победить вариант FIFO, она интегрирована сразу с Lambda по получению сообщения, лямбда что получает такое оповещение почему то не получает никогда сообщений. Вызывается лямбда по событию но сообщений в очереди уже почему то нет. Не знаю почему а было бы хорошо!! Ну сделать пришлось так - SQS Standard + Lambda Python + CloudWatch Cron каждые минут 10 проверяю очередь. Стоимость SQS+Lambda+Cloudwatch в таком использовании практически нулевая.

Лямбда:
---------------------
import json
import boto3
import re
queue_url = 'https://sqs.us-east-2.amazonaws.com/XXXXXXXX/Bname'
HostedZoneId = 'XXXXXXXXXX'
sqs_client = boto3.client('sqs')
ec2_resource = boto3.resource('ec2')
ec2_client = boto3.client('ec2')
r53_client = boto3.client('route53')

def add_cname_record(source, target):
    try:
        response = r53_client.change_resource_record_sets(
          HostedZoneId=HostedZoneId,
          ChangeBatch= {
            'Comment': 'add %s -> %s' % (source, target),
            'Changes': [{
            'Action': 'UPSERT',
            'ResourceRecordSet': {
              'Name': source+'.'+r53_client.get_hosted_zone(Id=HostedZoneId)['HostedZone']['Name'],
              'Type': 'CNAME',
              'TTL': 300,
              'ResourceRecords': [{'Value': target}]
            }
          }]
        }
       )
    except Exception as e:
       print(e)
    return

def get_messages_from_queue():
    #sqs_client = boto3.client('sqs')
    messages = []
    while True:
        resp = sqs_client.receive_message(
            QueueUrl=queue_url,
            AttributeNames=['All'],
            MaxNumberOfMessages=10
        )
     
        try:
            messages.extend(resp['Messages'])
        except KeyError:
            break

        #mm = json.dumps(messages)
        #print('MSG: %s' % mm)
        entries = [
            {'Id': msg['MessageId'], 'ReceiptHandle': msg['ReceiptHandle']}
            for msg in resp['Messages']
        ]

        resp = sqs_client.delete_message_batch(
            QueueUrl=queue_url, Entries=entries
        )

        if len(resp['Successful']) != len(entries):
            raise RuntimeError(
                f"Failed to delete messages: entries={entries!r} resp={resp!r}"
        )
    return messages

def get_ec2_name(instanceId,tag):
    ## return NAME of instance by instanceId tag (prefered Name)
    ec2instance = ec2_resource.Instance(instanceId)
    instanceName = ''
    for tags in ec2instance.tags:
        if tags["Key"] == tag:
            instanceName = tags["Value"]
    return instanceName

def set_ec2_tag_bname(instanceId,tagBname):
    ## set TAG tagName for instance by instanceId
    ids = []
    ids.append(instanceId)
    ec2_client.create_tags(
        Resources=ids,
        Tags=[
          {
            'Key': 'BName',
            'Value': tagBname
          }
        ]
    )
    return

def list_instanceIds_by_tag_value(tagkey, tagvalue):
    # When passed a tag key, tag value this will return a list of InstanceIds that were found.
    #print(tagkey)
    #print(tagvalue)
    response = ec2_client.describe_instances(
        Filters=[
            {
                'Name': 'tag:'+tagkey,
                'Values': [tagvalue]
            },
            {'Name': 'instance-state-name', 'Values': ['running']}
        ]
    )
    instancelist = []
    for reservation in (response["Reservations"]):
        for instance in reservation["Instances"]:
            instancelist.append(instance["InstanceId"])
    return instancelist

def list_instanceBnameIndex_by_tag_value(tagkey, tagvalue):
    # return list of digits each BNAME tag
    #print(tagkey)
    #print(tagvalue)
    response = ec2_client.describe_instances(
        Filters=[
            {
                'Name': 'tag:'+tagkey,
                'Values': [tagvalue+'*']
            },
            {'Name': 'instance-state-name', 'Values': ['running']}
        ]
    )
    instancelist = []
    for reservation in (response["Reservations"]):
        for instance in reservation["Instances"]:
            for tags in instance["Tags"]:
              if tags["Key"] == tagkey:
                 if tags["Value"]:
                   bname = re.findall('([0-9]+)$',tags["Value"])
                   bname = int(''.join(bname))
                   #print('II: %s' % bname)
                   instancelist.append(bname)
    instancelist.sort()

    return instancelist

def lambda_handler(event, context):
    #print('Start poll SQS')
    messages = get_messages_from_queue()
    if not messages:
        print('SQS is empty')
        return
   
    for message in messages:
        instanceId = message['Body']
        print('InstanceID: %s' % instanceId)
        if get_ec2_name(instanceId,'BName'):
           print('%s has BNAME!' % instanceId)
        else: 
           ids = []
           ids.append(instanceId)
           instanceName = get_ec2_name(instanceId,'Name')
           print('Tag Name of instance: %s' % instanceName)
           indexCurrentBname = []
           indexCurrentBname = list_instanceBnameIndex_by_tag_value('BName',instanceName)
           print('indexCurrentBname instances: %s' % indexCurrentBname)
           if not indexCurrentBname:
              print('No BNAME tags for %s. Set to 1' % instanceName)
              myFreeNumber = 1
           else:
              #print('IndexCurrentBname: %s' % indexCurrentBname)
              countAllInstancesByTagName = len(list_instanceIds_by_tag_value('Name', instanceName))
              print('countAllInstancesByTagName = %s' % countAllInstancesByTagName)
              fullArrayOfElements = list(range(1,countAllInstancesByTagName+1))
              #countFullArrayElement = len(list_instanceIds_by_tag_value('Name',instanceName))+1
              #fullArrayOfElements = list(range(1,countFullArrayElement))
              print('fullArrayOfElements %s' % fullArrayOfElements)

              for item in indexCurrentBname:
                  if item in fullArrayOfElements:
                     fullArrayOfElements.remove(item)

              myFreeNumber = min(fullArrayOfElements)
              freeNumbers = []
              for item in fullArrayOfElements:
                 freeNumbers.append(item)

              print('Current list of free elements: %s' % freeNumbers)
           print('Current free min number is: %s' % myFreeNumber)
           myTagName = instanceName+str(myFreeNumber)
           myTagName = myTagName.lower().replace(" ","")
           print('MyTagName will %s' % myTagName)
           set_ec2_tag_bname(instanceId,myTagName)
           add_cname_record(myTagName,ec2_resource.Instance(instanceId).private_dns_name)
         

-------------------------------------------------
Я не спец в питоне, пишу время от времени.
Что тут я задумал - читаю SQS до 10 сообещний максимум. В цикле отрабатываю каждое:
- тэг Name у каждого инстанца я не меняю (в задаче так было сказано). Зато я беру Name как основу для построения дополнительного тэга BName, который формируется как NameXX, где XX это уникальный номер в группе. Номер я ставлю минимальный свободный из списка номеров от 1 до текущего максимального в тэге BName. Так я не даю вырастать номерам до огромных цифр. Это решение позволило уникализировать и имена инстанцев в prometheus, ansible. В Ansible я использую связку -i /etc/ec2.py и hosts=tag_BName_yyyyyyyXX, для отработки на конкретном хосте или tag_Nane_yyyyyy что бы применить для всей группы хостов что либо.


Роль у хостов должна содержать право писать в SQS:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "sendSQSusBname",
            "Effect": "Allow",
            "Action": "sqs:SendMessage",
            "Resource": [
                "arn:aws:sqs:us-east-2:XXXXXX:Bname",
                "arn:aws:sqs:ap-northeast-1:XXXXXX:Bname"
            ]
        }
    ]
}









вторник, 26 февраля 2019 г.

Jenkins: Интерактивный выбор хостов для обработки в ansible с выборкой инстанцев по тэгам в AWS

Задачи специфичны, но идея может быть пригодится на будущее.
AWS + Spotinst. Приложение Java требует обновление порой на всех инстанцах, которых может быть несколько десятков. Люди привыкли обновлять приложения при помощи ansible и делать это так - снчала на один ставят обновление и смотрят как оно идет. Если хорошо то на пару следующих. Если все нормально то далее по пять штук обновляют. Для этого собирается список инстанцев и создается руками файлик hosts, заливается в git и оттуда уже этот файлик приезжает к ansible сценариям.

Вот не переделывая сложно структуру и привычки сделал Jenkins Job на базе Active Choise Plugin + Groovy.
Скрипт Groovy:
---------------------------------------
import groovy.json.JsonSlurper
def jsonSlurper = new JsonSlurper()
def command = 'aws ec2 describe-instances --filters Name=tag:Name,Values=production-instances Name=instance-state-name,Values=running --query Reservations[].Instances[].{release:State.Name,ip:PrivateIpAddress,name:Tags[?Key==\'BName\']|[0].Value}'

def proc = command.execute()
proc.waitFor()
def output = proc.in.text
def exitcode= proc.exitValue()
def error = proc.err.text
if (error) {
println "Std Err: ${error}"
println "Process exit code: ${exitcode}"
return exitcode
}

def object = jsonSlurper.parseText(output)
def names = object.name

def sm = []
object.each { entry ->
    def html = jsonSlurper.parseText("http://$entry.ip:8110/info".toURL().text)
    String rel = html.get("build.version")   
    entry.release = rel
    sm.add("$entry.name ($entry.ip): $rel")
}

return(sm)

------------------------------------
Здесь идея в том, что бы запросить все инстанцы со статусом Running и тэгом Name = production-instances, получить IP и значение тэга BName (в нем уникальные имена с цифрами в конце). Далее вернуть массив строк BName (IP) - ReleaseVersion
Этот массив строк выдется в виде Checkbox параметра для выбора тех инстанцев, которые будем обновлять. 

Шаг сборки - shell script:
--------------------------
#!/bin/bash
IFS=",";

bidderArray=($bidders);
hosts=""

for ((i=0; i<${#bidderArray[@]}; ++i)); do    
    IFS=" ";
    Bidders=(${bidderArray[$i]})
    name=$(echo ${Bidders[0]} | sed 's/-/_/g')
    hosts="${hosts}tag_BName_$name,"
done

hosts=$(echo ${hosts}|sed s/,$//)
ansible-playbook -i /usr/local/bin/ec2.py ping.yml --extra-vars "hosts=$hosts"
-------------------------------

Здесь парсинг массива строк и передача в переменную hosts для playbook.


четверг, 1 ноября 2018 г.

AWS. Create S3 folder (key), invoking Lambda for add partition for Athena

Забавно, но не так просто понять и найти оказалось как из cli сделать просто пустой ключ в S3.
У меня есть задача создавать партиции в Athena когда создается определенный ключ (hour=00 например). И в этот ключ будут падать файлы статистики в количестве сотен и тысяч штук.
MSCK REPAIR TABLE дело хорошее на на наших объемах и количествах это всегда работает максимально долго и валится не закончив дело.
Появилась идея навесить Event на S3 bucket, и вызывать лямбду для создания партиции. Но тут как раз и появляется тот самый момент, когда вешать событие на создание файла нет никакого смысла ибо это будет вызывать лямбду на каждый вызов PUT или CreateObject.
Моя цепочка это такой вид:

BUCKET/stat/year=YYYY/month=MM/day=DD/hour=HH/
Реагировать я хочу на создание любого нового ключа после /stat/.

Если создать событие такого типа:

Events: либо Put либо ObjectCreate(All) - тут нет разницы по-моему для этого случая. Я поставил PUT
Prefix: указываю ключ начала цепочки. В моем случае это stat/ В конце "/" прям обязателен
Suffix: /
SendTo: Lambda
Lambda ARN

Создавать ключ получилось вот так:
aws s3api put-object --bucket BUCKET_NAME --key stat/NEW_KEY --body ./null

важно так же, что ./null это просто пустой файл (touch ./null). /dev/null не сработает - зашито в cli и требует файла. И важно, после NEW_KEY не должно быть слэша, иначе null появится как файл

Лямбда: python 2.7

import logging, json
import boto3, botocore

logger = logging.getLogger()
logger.setLevel(logging.INFO)

s3 = boto3.client('s3')
athena = boto3.client('athena')
database = 'MY_DATABASE'
table_name = 'MY_TABLE'
s3_output = 's3://MY_ATHENA_RESULTS'

#Executing the athena query:
def run_query(query, database):
        query_response = athena.start_query_execution(
        QueryString=query,
        QueryExecutionContext={
            'Database': database
            },
        ResultConfiguration={
            'OutputLocation': s3_output
            }
        )
        print('Execution ID: ' + query_response['QueryExecutionId'])
        return query_response


def lambda_handler(event, context):
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    file_key = event['Records'][0]['s3']['object']['key']
    logger.info('Reading {} from {}'.format(file_key, bucket_name))

    checkIsHour="hour="
    if file_key.find(checkIsHour) != -1:
       logger.info('Found the hour key!')
       athena_prefix,athena_year,athena_month,athena_day,athena_hour,athena_null = file_key.split("/")
       logger.info('Prefix {} Year {} Month {} Day {} Hour {}'.format(athena_prefix,athena_year,athena_month,athena_day,athena_hour))
       query = str("ALTER TABLE " + table_name +" ADD IF NOT EXISTS PARTITION (" +
       athena_year + "," +
       athena_month + "," +
       athena_day + "," +
       athena_hour +
       ") location \"s3://"+ bucket_name
       + "/" + athena_year + "/" + athena_month + "/" + athena_day + "/"
       + athena_hour + "\";")
       logger.info('Query {}'.format(query))
       #print(query)
       run_query(query, database)



    

понедельник, 24 сентября 2018 г.

bash autocompleters on Amazon Linux

AWS:
which aws_completer
complete -C '/usr/bin/aws_completer' aws

K8S:
yum install --enablerepo=epel bash-completion
source /etc/bash-completion
source <(kubectl completion bash)

пятница, 20 апреля 2018 г.

AWS CodeCommit: доступ к репозиторию с использованием роли

Для роли добавляем полиси AWSCodeCommitReadOnly
далее под юзером jenkins например делаем:
git config --global credential.helper '!aws codecommit credential-helper $@'
git config --global credential.UseHttpPath true

и далее будет доступен вариант
git clone https://git-codecommit.us-east-1.amazonaws.com/v1/repos/terraform


пятница, 13 апреля 2018 г.

AWS. CLI. Setup S3 SSE for all buckets

Это появилось в aws cli с какой-то версии, проверяйте что бы в списке aws s3api была команда
put-bucket-encryption.
Ну и простой перебор:
#!/bin/bash
A=$(aws s3 ls|cut -d " " -f 3)
for i in $A
do
 echo $i
 aws s3api put-bucket-encryption --bucket ${i} --server-side-encryption-configuration='{"Rules": [{"ApplyServerSideEncryptionByDefault": {"KMSMasterKeyID": "arn:aws:kms:us-west-2:ACCOUNT_NUMBER:alias/aw
s/s3","SSEAlgorithm": "aws:kms"}}]}'
done


замените ACCOUNT_NUMBER на свой номер аккаунта, регион на свой да и имя ключа может быть другим. 

пятница, 30 марта 2018 г.

AWS. EC2 Squid как NAT с фильтрацией исходящих соединений по списку доменов с разрешением имен

Делаем EC2 в нужной VPC. ВЫКЛЮЧАЕМ Source/dest. check! Иначе раотать не будет да и не сможем даже указать этот инстанс как NAT в таблицах маршрутизации

Я собирал SQUID + LibreSSL, потому что с OpenSSL не работали некоторые запросы, которые использовали рсширения SSL (в частности 13-е).




yum groupinstall "Development Tools"
yum install libxml2-devel libcap-devel libtool-ltdl-devel libecap libecap-devel


wget http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.7.1.tar.gz

сборка и установка на место openssl

./configure --prefix=/usr
make
make install
ldconfig
openssl version

вывод должен показать: LibreSSL 2.7.1

wget ftp://dfw.squid-cache.org/pub/squid/squid-3.5.27.tar.gz

Сборка SQUID:

./configure \
--build=x86_64-redhat-linux-gnu --host=x86_64-redhat-linux-gnu --target=x86_64-amazon-linux-gnu \
--program-prefix= --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --sysconfdir=/etc --datadir=/usr/share --includedir=/usr/include --libdir=/usr/lib64 \
--libexecdir=/usr/libexec --sharedstatedir=/var/lib --mandir=/usr/share/man --infodir=/usr/share/info --disable-strict-error-checking --exec_prefix=/usr --libexecdir=/usr/lib64/squid \
--localstatedir=/var --datadir=/usr/share/squid --sysconfdir=/etc/squid \
--disable-dependency-tracking --enable-eui --enable-follow-x-forwarded-for \
--enable-external-acl-helpers=file_userip --enable-cache-digests --enable-cachemgr-hostname=localhost \
--enable-epoll --enable-ident-lookups --enable-linux-netfilter --enable-removal-policies=heap,lru \
--enable-ssl-crtd --enable-wccpv2 --enable-esi --enable-ecap --with-aio --with-default-user=squid --with-dl --with-openssl --with-pthreads \

--disable-arch-native


В качестве UserData:

INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
PRIV_IP=$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4)
AVZONE=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone);
REGION=${AVZONE::-1}

VPC_ID=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID --region $REGION --query "Reservations[0].Instances[0].VpcId")

SUBNETS=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=tag:Name,Values=*-EU1-Private-*" --region $REGION --query "Subnets[].CidrBlock" --output text)

iptables -F
iptables -t nat -F

iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3128
iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 3130

for i in $SUBNETS
do
 echo $i
 iptables -t nat -A POSTROUTING -o eth0 -s $i -j MASQUERADE
done

iptables -L -t nat
service iptables save

------------------------------------
Тут создаем iptables правила для редиректа http/https на Transparent Squid.
создайте юзера и группу squid.

/etc/squid/squid.conf:

cache_effective_user squid
cache_effective_group squid
dns_nameservers 8.8.8.8 8.8.8.4
visible_hostname squidPublic
cache deny all
http_port 123
http_port 3128 intercept
http_port 3129
acl Jenkins_Prod src 10.0.3.XX
acl NTP_PROD src 10.0.3.XXX
acl allowed_http_sites dstdomain .amazonaws.com
acl allowed_http_sites dstdomain .amazon.pool.ntp.org
acl allowed_http_sites dstdomain "/etc/squid/domains.lst"
acl allowed_http_sites dstdomain 169.254.169.254
acl allowed_http_sites dstdomain packages.us-west-1.amazonaws.com
http_access allow localhost manager
http_access deny manager
http_access allow Jenkins_Prod
http_access allow NTP_PROD
http_access allow allowed_http_sites

https_port 3130 cert=/etc/squid/ssl/squid.pem ssl-bump intercept
acl SSL_port port 443
http_access allow SSL_port
acl allowed_https_sites ssl::server_name .amazonaws.com
acl allowed_https_sites ssl::server_name "/etc/squid/domains.lst"
acl step1 at_step SslBump1
acl step2 at_step SslBump2
acl step3 at_step SslBump3
ssl_bump peek step1 all
ssl_bump peek step2 allowed_https_sites
ssl_bump splice step3 allowed_https_sites
ssl_bump terminate step2 all

http_access deny all
logfile_rotate 10
#debug_options rotate=10
access_log none
#access_log daemon:/var/log/squid/access.log
cache_log /var/log/squid/cache.log
---------------------------------
я выключил логи - мне не надо.

файлик squid.conf заточен на использование списка доменов в /etc/squid/domains.lst
это простой текстовик с доменами для резолва, например:
.oracle.com
.jenkins.io
.bitbucket.org
.packagist.org
.osuosl.org
.jenkins-ci.org
repo.zabbix.com
.amazonaws.com
.cloudfront.net
.github.com

----------------
В файлик старт-стоп я добавил копирование списка доменов с S3.
aws s3 cp s3://domainlist-squid/domains.lst /etc/squid

---------------
еще /etc/sysctl.conf
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.tcp_max_orphans = 65536
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_max_syn_backlog = 65536
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_mem = 50576 64768 98152
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_orphan_retries = 0
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_congestion_control = htcp
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.route.flush=1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.lo.rp_filter = 1
net.ipv4.conf.eth0.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.lo.accept_source_route = 0
net.ipv4.conf.eth0.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_rfc1337 = 1
net.ipv4.ip_forward = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.core.somaxconn = 262144
net.core.netdev_max_backlog = 1000
net.core.rmem_default=65536
net.core.wmem_default=65536
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

----------------------
Ну и старндартно в таблицы маршрутов для приватных сетей на маршрут 0.0.0.0/0 вместо NAT выбираем из списка наш инстанс.


пятница, 16 марта 2018 г.

Ubuntu. Разные Java из скрипта

Например для пакера как вариант:
Здесь потребовалось поставить две версии JRE 8u74 и 8u131. В зависимости от роли инстанса нужно выставить версию.
По умолчанию делаю 131-й а в UserData CloudFormation выставляется 74-я для одной из ролей.
JRE скачал сначала с сайта и уложил на S3.
#!/bin/bash
mkdir -p /usr/lib/jvm | true
apt-get install -y java-common

## JRE versions
JV="jre-8u74 jre-8u131"
mkdir -p /tmp/java | true
cd /tmp/java
for i in ${JV}
do
 aws s3 cp s3://xxxxxx-apps/java/${i}-linux-x64.tar.gz /tmp/java/ --region xx-xxxxx-2
 mkdir -p /usr/lib/jvm/${i}
 tar -zxf /tmp/java/${i}-linux-x64.tar.gz -C /usr/lib/jvm/${i} --strip-components=1
 update-alternatives --install /usr/bin/java java /usr/lib/jvm/${i}/bin/java 1000
done

cd /
rm -rf /tmp/java | true
echo "Setup Java 8U131 as default"
update-alternatives --set java /usr/lib/jvm/jre-8u131/bin/java

четверг, 1 февраля 2018 г.

AWS cli, terraform multi account config based on prifiles

Когда моно аккаунтов в работе то невольно начинаешь придумывать как их разделять.
Один из вариантов покажу, что мне пока нравится.
Нужно скачать awscli, terraform, git
~/.aws/config
[default]
region = us-east-1
[profile profile1]
[profile profile2]


~/.aws/credentials
[default]

[profile1]
aws_access_key_id = xxxxx
aws_secret_access_key = yyyy

[profile2]
aws_access_key_id = zzzz
aws_secret_access_key = ccc


Далее ставим export AWS_PROFILE=profile1
для awscli и Terraform этого достаточно



вторник, 12 декабря 2017 г.

AWS. Наборчик запросов для разных случаев

INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
AVZONE=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone);

REGION=${AVZONE::-1}

REGION=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | grep region | awk -F\" '{print $4}')

PRIV_IP=$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4)

Получить VpcID в которой эта EC2 работает:
aws ec2 describe-instances --instance-ids EC2_ID --region REGION --query "Reservations[0].Instances[0].VpcId"

Получить список InstanceId по тэгам Name=httpfront-01 и Environment=dev:
aws ec2 describe-instances --region eu-west-2 --filters "Name=tag-key,Values=Name" "Name=tag-value,Values=httpfront-0?" "Name=tag-key,Values=Environment" "Name=tag-value,Values=dev"  --query "Reservations[].Instances[].InstanceId" --output text

Тоже но получить только имена из тэгов:
aws ec2 describe-instances --region eu-west-2 --filters "Name=tag-key,Values=Name" "Name=tag-value,Values=httpfront-0?" "Name=tag-key,Values=Environment" "Name=tag-value,Values=dev"  --query "Reservations[].Instances[].Tags[?Key=='Name'].Value[]"

Получить CIDR приватный подсеток в VPC, где находится EC2:
SUBNETS=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=tag:Name,Values=*-Private-*" --region $REGION --query "Subnets[].CidrBlock" --output text)

Список лимитов по регионам: (взято здесь https://alestic.com/2013/12/ec2-account-attributes/)
regions=$(aws ec2 describe-regions --output text --query 'Regions[*].RegionName')
attributes="max-instances max-elastic-ips vpc-max-elastic-ips"
for region in $regions; do
  echo; echo "region=$region"
  aws ec2 describe-account-attributes --region $region --attribute-names $attributes --output text --query 'AccountAttributes[*].[AttributeName,AttributeValues[0].AttributeValue]' |
    tr '\t' '=' | sort
done

Список инстанцев в состоянии Runing:
aws ec2 describe-instances --query "Reservations[].Instances[].InstanceId" --filters "Name=instance-state-name,Values=running"

или в столбик без кавычек и прочего JSON:

aws ec2 describe-instances --query "Reservations[].Instances[].[InstanceId]" --filters "Name=instance-state-name,Values=running" --output text

FILTER/QUERY (http://jmespath.org/specification.html)
aws ec2 describe-snapshots --region us-east-1 --query "Snapshots[?contains(Description,'__prod-mongo-')]"
aws ec2 describe-snapshots --region us-east-1 --query "Snapshots[?ends_with(Description,'i-0749934234499394f__prod-mongo-1')]"

воскресенье, 8 октября 2017 г.

Выполнить несколько команд в докер контейнерах AWS ECS в ASG

Есть AWS ASG с ECS. Там не понятно сколько инстансев.
Задача - выполнить очистку opcache на каждом php5-fpm через sock.

Получить все адреса активных инстансев в ASG я уже описывал как. Кратко:

ASGName="awseb-xxxxxx-stack-AWSEBAutoScalingGroup-YYYYYYYYYY"
aws ec2 describe-instances --filters "Name=tag:aws:autoscaling:groupName,Values=${ASGName}" "Name=instance-state-name,Values=running" --region us-west-2 | jq "
.Reservations[].Instances[].PrivateIpAddress"|cut -d "\"" -f 2 >/tmp/origins.lst

далее такой скрипт:
#!/bin/sh
for host in $(cat /tmp/origins.lst);
 do ssh -i my_key.pem ec2-user@$host  << EOF
sudo -i
docker exec \$(docker ps -aqf "name=ecs-aws") bash -c 'cd /tmp; curl -sO http://gordalina.github.io/cachetool/downloads/cachetool.phar; /usr/bin/php cachetool.phar o
pcache:reset --fcgi=/var/run/php5-fpm.sock'
EOF
done

PS:
docker ps -aqf "name=ecs-aws" - у меня был Beanstalk с преконфигурённым MultiDocker container platform. Это означает, что у меня запущено как минимум два контейнера. первый - это ecs-agent а второй уже мой рабочий php5-fpm. По фильтру ecs-aws выбирается как раз НЕ agent, то есть мой контейнер.

PSS: 
очистить memcache полностью:
telnet memcache 11211
flush_all
quit



среда, 2 августа 2017 г.

ELK на базе AWS ElasticSearch. Access Policy

Потратил время что бы разобраться. Больше не хочу! Запишу сюда.
При создании ElasticSearch мы получаем кроме всего прочего (нажать пару кнопок и на тебе, готово) набор проблем.
1. Enadpoint светит наружу!
2. Кибана светит наружу!
3. Доступ по Access Policy.... 

У меня в приватных сетях сидят инстансы, которые шлют логи в Logstash, а логстэш их уже отправляет в ES. Клиент хочет (и вполне резонно) что бы кибана наружу не светилась.

Закрываем:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::xxxxxxx:role/ProductionLogstash-logstash-role"
        ]
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:us-west-2:xxxxxxxx:domain/elk/*"
    },
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": [
        "es:ESHttpGet",
        "es:ESHttpPut",
        "es:ESHttpPost",
        "es:ESHttpHead"
      ],
      "Resource": "arn:aws:es:us-west-2:xxxxxxx:domain/elk/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": [
            "NAT1_IP",
            "NAT2_IP"
            "127.0.0.1"
          ]
        }
      }
    }
  ]
}

Здесь адреса NAТ потому что из приватных сеток к Endpoint ES мы пойдем через NAT!
Роль, что выделена красным - роль на инстанс Logstash.

Прокси ставится так:
#curl -o- https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
#nvm install node v6.11.2
#npm install -g aws-es-kibana
#aws-es-kibana -b IP -p 9200 -r us-west-2 kibana.domain.local -s


понедельник, 31 июля 2017 г.

AWS. Encrypted Boot volume. Beanstalk PreConfigured Multicontainer Docker

Задача по шифрованию томов может возникать по разным причинам. Но чаще всего при выставлении требований - соответствие HIPAA или наличие PHI.
Есть дизайн, в котором несколько тиров на базе BeansTalk MultiContainer Docker.
После деплоймента неожиданно всплывает требование по шифрации томов.
Переделывать ничего не хочется. А задача упирается на первый взгляд в тупик. В CloudFormation нет опций для указания шифрования томов. Вообще! Не поддерживается такая возможность пока.
Немного общих тезисов:
Preconfigured Beanstalk AMI это не простой AMI, а AMI со специфичным набором в UserData. Это набор команд, файлов и скриптов, которые разворачиваются при старте инстанса в бинстолк окружение. Это означает для нас что просто так взять AMI из развернутого окружения нельзя. Уже поздно его брать, потому что оно уже модифицировано скриптами.
Кроме этого, скриптами будет подтянут дополнительный том EBS для Docker. А нам надо зашифровать и его. Есть несколько подходов к решению задачи.
Один из них это подготовка EBS томов заранее, с шифрованием и подключение их к уже развернутым инстансам с последующим клонированием содержимого.
Я отправился по другому пути. Не факт что он лучше, но в моей задаче отработал.
Смысл его в подготовке AMI с уже зашифрованными томами KMS.

Смотрим какой AMI был использован при разворачивании EBT (ElasticBeanstalk). Это смотрим в Description на любую EC2 в рабочем окружении EBT, которое хотим модифицировать.
Идем в консоль EC2, говорим Launch, в качестве AMI ищем и выбираем нужный нам. Он будет в разделе Community AMIs скорее всего.
В UserData ОБЯЗАТЕЛЬНО добавим:
#cloud-config
repo_upgrade: none
repo_releasever: 2017.03

это заморозит процесс обновления и скриптов при старте инстанса, пока мы его подготавливаем. repo_releaserver: здесь цифра 2017.03 вычисляется из имени родительского AMI. В описании AMI указано какой год и месяц релиза был использован для его создания. Вот эти цифры и берем.
Запускаем Launch, ждем завершения создания инстанса. Смотрим в его описание в раздел о томах. Сейчас есть только один том - root, /dev/xvda. И он не зашифрован, как и ожидалось.
Останавливаем инстанс (Stop). Идем в раздел Volumes. Находим наш том и делаем ему Snapshot. После создания Snapshot так же будет НЕ зашифрован. Но это этап и его делать придется. Далее выбираем созданный SnapShot , Action -> Copy. Ставим крыжик Encryption!
Вот теперь у нас есть снимок зашифрованый нашим KMS (если сделали отдельный ключ для этих целей, выбирайте его при копировании).

Важно - перед следующим действием смотрите в какой AZ лежит наша EC2 instance, которую мы готовим.
Теперь из этого снимка Action -> Create Volume.  Здесь выбрать нужно ту AZ, где и наша EC2!
Теперь у нас есть том, он зашифрован ключом KMS, и это копия нашего Boot Volume.
Находим теперь наш том, который сейчас является текущим и активным Boot Volume для нашей Ec2, говорим ему Detach. На его место нужно подключить наш новый зашифрованный том с тем же путем - /dev/xvda

Теперь грязный хак. Создаем НОВЫЙ том, сразу зашифрованый нашим ключом KMS, в моем случае это том 12GB gp2. Я его подключаю к нашей EC2 по пути /dev/xvdcz - именно такой том создают скрипты EBT Preconfigured Docker Multicontainer.

Идем в раздел UserData и убираем наши строчки что мы добавляли ранее. Там должно быть чисто.

Все. теперь делаем из нашей EC2 новый AMI. Этот AMI теперь можно добавить в CFT EBT в OptionSettings ->
{"Namespace" : "aws:autoscaling:launchconfiguration", "OptionName" : "ImageId", "Value": "ami-XXXXXX"}

Делаем Update Stack CloudFormation. Новые инстансы будут на базе нашего AMI и оба тома будут зашифрованы.




суббота, 22 июля 2017 г.

AWS. ElastiCache VPC. Redis, Memcache. Conditions

Задача:
Сделать CloudFormation шаблон в структуре наследования (Nested templates), в котором будет создаваться VPC + SG а потом набор сервисов, но я покажу на примере одного - ElastiCache.
Так же в задаче указано, что в шаблоне нужно сделать выбор в параметрах Memcache/Redis, в зависимости от окружения выбрать тип узлов и их количество.

Проблем с Nested, Memcache не возникло. Проблем было много при создании Redis.
И вот почему.
Redis можно сделать в режимах Cluster enabled/disabled. Так же можно использовать консоль для создания или CLI/SDK. А в документации для консоли и CLI даже параметры именуются по разному. Да еще и для создания кластеров тип ресурса один описан а создание Redis не поддерживается этим сопособом. Я имею ввиду "Type": "AWS::ElastiCache::CacheCluster".
По сути чем отличается и что выбрать при создании Redis описано в документации AWS.
Я покажу живой пример создания. Ибо для создания Redis нужен другой тип, а именно "Type" : "AWS::ElastiCache::ReplicationGroup". В примере будет создан Redis MultiAZ, Cluster Disabled (не трудно поменять). Так же покажу пример Outputs для ReplicationGroup.
Мастер шаблон (простой пример):

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Master-example template for test purpose only.",
"Parameters": {
"EnvName": {
"Type": "String",
"Default": "Staging",
"AllowedValues": [
"Prod",
"Staging"
],
"Description": "Purposes of created environment"
},
"VpcName": {
"Default": "MyVPC",
"Description": "VPC Name",
"Type": "String"
},
"KeyName": {
"Type": "AWS::EC2::KeyPair::KeyName",
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances",
"MinLength": 1
},
"VpcCidrBlock": {
"Default": "10.0.0.0/16",
"Description": "VPC CIDR Block",
"Type": "String"
},
"AZs": {
"Default": "us-west-2a,us-west-2b,us-west-2c",
"Description": "The list of active AZ's. To use this template need to specify 3 AZ's (A,B and C).",
"Type": "List"
},
"SubnetsSharedPrivate": {
"Type": "CommaDelimitedList",
"Default": "10.0.96.0/22, 10.0.100.0/22, 10.0.104.0/22",
"Description": "IP Ranges (CIDR) for Shared Private Subnets of each A,B,C AZ's"
},
"CacheEngine": {
"Type": "String",
"Default": "Redis",
"AllowedValues": [
"Redis",
"Memcache"
],
"Description": "ElastiCache Engine"
}
},
"Mappings": {},
"Conditions": {},
"Resources": {
"VPC": {
           "Type": "AWS::CloudFormation::Stack",
           "Properties": {
               "TemplateURL": "https://s3.amazonaws.com/xxxx/CF/vpc-example.json",
   "Parameters": {
                            "VpcName" : { "Ref": "VpcName" },
    "EnvName" : { "Ref": "EnvName" },
    "SubnetsSharedPrivate" : {"Fn::Join": [ ",", { "Ref": "SubnetsSharedPrivate" }]},
    "AZs" : {"Fn::Join": [ ",", { "Ref": "AZs" }]},
    "VpcCidrBlock" : { "Ref" : "VpcCidrBlock" }
                    },
                    "TimeoutInMinutes": "60"
               }
            },
   "ElastiCache": {
           "Type": "AWS::CloudFormation::Stack",
           "Properties": {
               "TemplateURL": "https://s3.amazonaws.com/xxxx/CF/ElastiCache.json",
   "Parameters": {
 "EnvName" : { "Ref": "EnvName" },
 "CacheEngine" : { "Ref": "CacheEngine" },
 "CacheSecurityGroup" : { "Fn::GetAtt" : [ "VPC", "Outputs.SecurityGroup" ] },
 "SubnetsPrivateIds" : { "Fn::GetAtt" : [ "VPC", "Outputs.SubnetsPrivateIds" ] }

               },
               "TimeoutInMinutes": "60"
           }
       }
   },
   "Outputs": {
       "VpcId": {
           "Value": { "Fn::GetAtt" : [ "VPC", "Outputs.VpcId" ] }
       },
  "SubnetsPrivateIds": {
           "Value": { "Fn::GetAtt" : [ "VPC", "Outputs.SubnetsPrivateIds" ] }
       },
  "ElastiCacheEndpoint": {
"Value": { "Fn::GetAtt" : [ "ElastiCache", "Outputs.ElastiCacheEndpoint" ] }
  }
   }
}

-------------------------
это часть шаблона vpc-example.json, секция Outputs. Она нам очень важна для передачи параметров в другие шаблоны по порядку.
"Outputs": {
"VpcId": {
"Value": { "Ref": "Vpc" },
"Description": "ID for VPC"
},
"VpcCidrBlock": {
"Description": "CIDR block of created VPC",
"Value": { "Ref": "VpcCidrBlock" }
},
"SubnetsSharedPrivateIds": {
"Value": { "Fn::Join": [ ", ", [ { "Ref": "SubnetPrivateA" }, { "Ref": "SubnetPrivateB" }, { "Ref": "SubnetPrivateC" } ] ] },
"Description": "IDs for private subnets (A/B)"
},
"SecurityGroup": {
"Value": { "Ref": "SecurityGroup" },
"Description": "SG"
}
}

-----------------------------
Это ElastiCache.json

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ElastiCache. This template will be create ElastiCache cluster. Needed Environment Name, Engine, Subnets and Security Group. v07.21.2017-01",
"Conditions": {
"CreateProdResources" : {"Fn::Equals" : [{"Ref" : "EnvName"}, "Prod"]},
"CreateRedisResources" : {"Fn::Equals" : [{"Ref" : "CacheEngine"}, "Redis"]},
"CreateMemcacheResources" : {"Fn::Equals" : [{"Ref" : "CacheEngine"}, "Memcache"]}
},
"Mappings" : {  "Environments" : {
"Prod" : {
"InstanceType" : "cache.m3.xlarge",
"CountNodes" : "2"
},
"Staging" : {
"InstanceType" : "cache.m3.medium",
"CountNodes" : "2"
}
}
},
"Parameters": {
"EnvName": {
"Type": "String",
"AllowedValues": [
"Prod",
"Staging"
],
"Description": "Purposes of created environment"
},
"CacheEngine": {
"Type": "String",
"AllowedValues": [
"Redis",
"Memcache"
],
"Description": "ElastiCache Engine"
},
"CacheSecurityGroup": {
"Description": "Security group for Cache Cluster",
"Type": "String"
},
"SubnetsPrivateIds": {
"Description": "Shared Private Subnets",
"Type": "List"
}
},
"Metadata": {},
"Resources": {
"CacheSubnetGroup": {
"Type": "AWS::ElastiCache::SubnetGroup",
"Properties": {
"Description": "Cache Subnet Group",
"SubnetIds": { "Ref": "SubnetsPrivateIds" }
}
},
"RedisCluster" : {
"Condition" : "CreateRedisResources",
"Type" : "AWS::ElastiCache::ReplicationGroup",
"Properties" : {
"AutomaticFailoverEnabled" : true,
"CacheNodeType" : { "Fn::FindInMap" : ["Environments", { "Ref" : "EnvName"}, "InstanceType"] },
"CacheSubnetGroupName" : { "Ref" : "CacheSubnetGroup" },
"Engine" : "redis",
"ReplicationGroupId" : { "Fn::Join": ["",[{"Ref": "EnvName"},"-redis"]]},
"ReplicationGroupDescription" : "Redis (cluster mode disabled)",
"NumCacheClusters": { "Fn::FindInMap" : ["Environments", { "Ref" : "EnvName"}, "CountNodes"] },
"Port" : 6379,
"CacheParameterGroupName" : "default.redis3.2",
"SecurityGroupIds" : [{ "Ref" : "CacheSecurityGroup" }],
"Tags" : [
{"Key" : "Environment", "Value" : {"Ref" : "EnvName"}}
]
}
},
"MemcacheCluster" : {
"Condition" : "CreateMemcacheResources",
"Type": "AWS::ElastiCache::CacheCluster",
"Properties" : {
"AZMode" : "cross-az",
"ClusterName" : { "Fn::Join": ["",[{"Ref": "EnvName"},"-memcache"]]},
"CacheNodeType" : { "Fn::FindInMap" : ["Environments", { "Ref" : "EnvName"}, "InstanceType"] },
"Engine" : "memcached",
"NumCacheNodes" : { "Fn::FindInMap" : ["Environments", { "Ref" : "EnvName"}, "CountNodes"] },
"VpcSecurityGroupIds"  : [{ "Ref" : "CacheSecurityGroup" }],
"CacheSubnetGroupName" : { "Ref": "CacheSubnetGroup" },
"Tags" : [
{"Key" : "Environment", "Value" : {"Ref" : "EnvName"}}
]
}
}

},
"Outputs": {
"ElastiCacheEndpoint": {
"Value": {
"Fn::If" : [ "CreateMemcacheResources",

{ "Fn::Join": [ ":", [
{ "Fn::GetAtt": [ "MemcacheCluster", "ConfigurationEndpoint.Address" ] },
{ "Fn::GetAtt": [ "MemcacheCluster", "ConfigurationEndpoint.Port" ] }
]]
},
{ "Fn::Join": [ ":", [
{ "Fn::GetAtt": [ "RedisCluster", "PrimaryEndPoint.Address" ] },
{ "Fn::GetAtt": [ "RedisCluster", "PrimaryEndPoint.Port" ] }
]]
}
]
}
}
}
}

------------------
В итоге создается (если выбран Engine = Redis):
Shards: 1
Nodes: 2
MultiAZ: enabled
Engine: redis.
Поменяте значение этого параметра с  "CacheParameterGroupName" : "default.redis3.2" на default.redis3.2.cluster.on для создания Redis Clustered Engine. Так же нужно будет добавлять пареметры по количеству кластеров + количеству шардов и нод в шардах.

понедельник, 17 июля 2017 г.

AWS. CloudFormation. RDS Aurora Cluster and DeletePolicy

При попытке сделать посредством CloudFormation RDS Aurora Cluster с указанием "DeletionPolicy" : "Snapshot" в регионе Oregon стал получать ошибку (при рабочем шаблоне на прошлой же неделе, просто создаем новое окружение):

DeletionPolicy:Snapshot cannot be specified for a cluster instance, use deletion policy on the cluster instead.

Вообщем то явный глюк AWS. Исправить придется как оно того сама же хочет:
В секции ресурсов для DBCluster:

"RDSCluster" : {
      "Type" : "AWS::RDS::DBCluster",
      "Properties" : {
"MasterUsername" : {"Ref": "DBUser"},
"MasterUserPassword" : {"Ref": "DBPassword"},
"Engine" : "aurora",
                "DatabaseName" : {"Ref": "DBNameParam"},
                "DBSubnetGroupName": {"Ref": "AuroraDBSubnetGroup"},
                 "StorageEncrypted" : true,
"VpcSecurityGroupIds" : [{"Ref": "DBSecurityGroup"}],
"Tags" : [
                    {"Key" : "Environment", "Value" : {"Ref" : "EnvName"}},
                {"Key" : "Name", "Value" : {"Fn::Join": ["",[{"Ref" : "DBNameParam"},"-",{"Ref" : "EnvName"}]]}}
                 ]
          },
 "DeletionPolicy" : "Snapshot"
    }

В секции DBInstance придется добавить:
"DBInstance" : {
      "Type" : "AWS::RDS::DBInstance",
 "DeletionPolicy" : "Delete",
      "Properties" : {
......
}

понедельник, 3 июля 2017 г.

AWS: Linux, SimpleAD, sssd

Настроить аутентикацию пользователей в линуксе через SimpleAD можно с помощью например SSSD. Работая с этой связкой получил вот такую проблему.
Создавая пользователя, он по умолчанию будет в состоянии Disabled, о чем в /var/log/secure можно будет увидеть что-то типа:
pam_sss(sshd:auth): received for user Domain\userName: 13 (User account has expired)

а в /var/log/messages:
 [sssd[krb5_child[2873]]]: Clients credentials have been revoked

Это как оказалось потому, что в схеме будет поле userAccountControl: 514
А нужное значение = 512
514 означает Account is disabled.
512 - Enabled.
После создания пользователя

#net ads user add kino password -C "Alex Loo" -S domain.com -U Administrator%SUPER_PASSWD

я сделал файлик для модификации схемы:
# more uac.ldif
dn: CN=Alex Loo,CN=Users,DC=domain,DC=com
changetype: modify
replace: userAccountControl
userAccountControl: 512

и применил изменения:
#ldapmodify -f uac.ldif -h domain.com -p 389 -D 'cn=Administrator,cn=Users,dc=domain,dc=com' -W
Enter LDAP Password:
modifying entry "CN=Alex Loo,CN=Users,DC=prod,DC=local"

Как бы сделать так, что бы было сразу 512 для всех новых пользователей?
Схему умолчания менять похоже нужно.

PS: userdata script for automatically join to domain:
#!/bin/bash
DOMAIN="ws.local"
SAD_NS1=172.19.11.21
SAD_NS2=172.19.12.12
HOSTNAME=`hostname`
DOMAINNAME=`hostname -d`
# currently, we don't have Environment tag.
ENVIRONMENT="dev"
CURRENT_NS=$(grep nameserver /etc/resolv.conf |head -1|cut -d  " " -f 2)

set -e
sudo yum -y update
yum -y install dnsmasq
cat >> /etc/dnsmasq.conf << EOF
domain=${DOMAINNAME}
server=/${DOMAIN}/${SAD_NS1}
server=/${DOMAIN}/${SAD_NS2}
server=/${DOMAINNAME}/${CURRENT_NS}
EOF
echo "prepend domain-name-servers 127.0.0.1,${CURRENT_NS};" >> /etc/dhcp/dhcpclient.conf
cat > /etc/resolv.conf << EOF
nameserver 127.0.0.1
nameserver ${CURRENT_NS}
EOF
service dnsmasq start

sudo yum -y install sssd realmd krb5-workstation samba-common openldap-clients adcli
#for ubuntu:
#apt install sssd realmd packagekit krb5-user samba-common adcli sssd-tools sssd libnss-sss libpam-sss

echo "PASSWORD"|realm join -U join@${DOMAIN} ${DOMAIN} --computer-ou=OU=ec2,ou=${ENVIRONMENT},dc=sad,dc=ws,dc=local --verbose
#sed -i "3 a default_domain_suffix = ${DOMAIN}" /etc/sssd/sssd.conf
sed -i -e "s/\(use_fully_qualified_names = \).*/\1False/" /etc/sssd/sssd.conf
sed -i -e "s|\(fallback_homedir = \).*|\1 /home/%u|" /etc/sssd/sssd.conf
# allowing access to members of group linux.groups.dev.sad.ws.local
echo "ad_access_filter = (memberOf=cn=linux,ou=groups,ou=${ENVIRONMENT},dc=sad,dc=ws,dc=local)" >> /etc/sssd/sssd.conf
service sssd restart
realm permit --all
# enabling sudo
echo '%domain\ users ALL=(ALL:ALL) NOPASSWD:ALL' > /etc/sudoers.d/100-domain-users
sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config
service sshd restart