AWS Secret ManagerをPython3で使ってみる

Secret Managerを使っていきます。
今回はkeyとvalueにします。

secret nameも設定します。

RDSのpassだとrotationができるらしい。このrotationって機能は面白いですね。

iamでSecret Managerのread/writeの権限を付与します。

### Pythonで取得してみる
$ pip3 install boto3
L boto3.client の書き方は、s3へのget/uploadとほぼ同じ。あとは、client.get_secret_valueとするだけ。

import boto3
import base64
from botocore.exceptions import ClientError
 
accesskey = ""
secretkey = ""
region = "ap-northeast-1"
secret_name = "stg/beta/name"

  
client = boto3.client('secretsmanager', aws_access_key_id=accesskey, aws_secret_access_key= secretkey, region_name=region)
  
try:
    get_secret_value_response = client.get_secret_value(
        SecretId=secret_name
    )
except ClientError as e:
    if e.response['Error']['Code'] == 'DecryptionFailureException':
        raise e
    elif e.response['Error']['Code'] == 'InternalServiceErrorException':
        raise e
    elif e.response['Error']['Code'] == 'InvalidParameterException':
        raise e
    elif e.response['Error']['Code'] == 'InvalidRequestException':
        raise e
    elif e.response['Error']['Code'] == 'ResourceNotFoundException':
        raise e
else:
    if 'SecretString' in get_secret_value_response:
        secret = get_secret_value_response['SecretString']
    else:
        decoded_binary_secret = base64.b64decode(get_secret_value_response['SecretBinary'])

print(get_secret_value_response)

response
{‘ARN’: ‘arn:aws:secretsmanager:ap-northeast-1:702423327513:secret:stg/beta/name-FgtB23’, ‘Name’: ‘stg/beta/name’, ‘VersionId’: ‘cbe2dfcb-2e5b-4032-a919-a6213f3436db’, ‘SecretString’: ‘{“name”:”hpscript”}’, ‘VersionStages’: [‘AWSCURRENT’], ‘CreatedDate’: datetime.datetime(2022, 2, 11, 23, 43, 4, 732000, tzinfo=tzlocal()), ‘ResponseMetadata’: {‘RequestId’: ‘3c41a108-4cee-46a4-aa9f-af3d2252637c’, ‘HTTPStatusCode’: 200, ‘HTTPHeaders’: {‘x-amzn-requestid’: ‘3c41a108-4cee-46a4-aa9f-af3d2252637c’, ‘content-type’: ‘application/x-amz-json-1.1’, ‘content-length’: ‘265’, ‘date’: ‘Sat, 12 Feb 2022 00:02:59 GMT’}, ‘RetryAttempts’: 0}}

おおおおおおおおおおおおおおおおおおおお

AWS Secret Managerとは?

機密情報の管理を簡単にするAWSマネージド・サービス
アプリケーション内のハードコードされた機密情報を削除する
JSONで格納できる。最大4096バイト。
e.g. サーバ名、IPアドレス、ポート番号、データベース接続情報、APIキーなど

機密情報の構造

メタ情報
– 機密情報の名前、説明、ARN
– 暗号化、複合化に利用するAWS Key
バージョン情報
– 変更時には新バージョンが作成される
– 各バージョンには暗号化された値のコピーが保持
– 機密情報がローテーション上のどこにあるかを識別するステージングラベルが添付

月額$0.40
1万APIコールで$0.05

リクエストが多い場合は考慮する必要がある。

[AWS Glacier] 入門

アーカイブ削除をロック

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Deny",
			"Principal" : {
				"AWS" : "*"
			},
			"Action": "glacier:DeleteArchive",
			"Resource": "arn:aws:glacier:ap-northeast-1:00000000000:vaults/naata-Glacier"
		}
	]
}

1. Create Vault
2. Enable notifications and create a new SNS topic
3. Event Notifications Details
Glacier-event-test
Archive Retrieval Job Complete : on
Vault Inventory Retrieval Job Complete : on

### S3のGlacier自動アーカイブ機能を設定する
S3のGlacier自動アーカイブ機能は頻繁に使用するデータはS3に保存しておき、ルールを設定して自動的に古くなったデータをGlacierのストレージにアーカイブする

1. S3に任意のフォルダを作成する
2. Create Lifecycle rule


ライフサイクルルールを設定して運用するイメージはわかった。

[AWS Glacier] 基礎

GlacierはS3の中でも長期保存向けのサービス
Glacier(氷河)は使用頻度の低いファイルを長期的に保存するためのサービス
1GBあたり$0.004/月とS3の$0.019/月 より安い

### アーカイブ
AWS S3 Glacierではアーカイブに保存される
個別容量は40TB以下まで
アーカイブに入れたファイルは変更不可

### ボールド
コンテナとしてアーカイブをAWS S3 Glacierにまとめてグループ化することができる

[重要]データの取り出しに約4~5時間かかる

– すぐにダウンロードしなければならないファイルやよく取り出すファイルなどはAmazon S3
– ほとんど取り出す機会がないデータやダウンロードに時間が掛かってもいいデータはAmazon Glacier

なるほどー 
なんか どうなってんだコレは…

[AWS CloudFront] 入門

CloudFrontでVPCを作成する

01_create_vpc.yaml

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  FirstVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16

CloudFrontでCreate Stackとします。

作成したyamlファイルをuploadします。

Option等は設定せずに、create stackとします。
すると、cloudFormationで作成されたVPCが出来上がっています。
なお、cloudformationのstackを削除すると、vpcも削除されます。

CloudFormationの項目
– Format Version
– Description
– Metadata
– Parameters
– Mappings
– Resources
– Outputs

Resources部分

Resources:
	<Logical ID>:
		Type: <Resource type>
		Properties:
			<Set of properties...>

Logical IDはテンプレート内で一意のID
Resource typeは作成するリソースのタイプ
Resource propertiesはリソースの作成時に指定するプロパティ

リソースに命名する時

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  FirstVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
      - Key: Name
        Value: first-VPC

stackをupdate

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  FirstVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
      - Key: Name
        Value: first-VPC
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: FirstVPC-IGW
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref FirstVPC
      InternetGatewayId: !Ref InternetGateway
  FrontendRouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref FirstVPC
      Tags:
      - Key: Name
        Value: FirstVPC-FrontendRoute
  FrontendRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref FrontendRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  FrontendSubnet:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      CidrBlock: 10.0.1.0/24
      MapPulicIpOnLaunch: 'true'
      VpcId: !Ref FirstVPC
      Tags:
      - Key: Name
        Value: FirstVPC-FrontendSubnet
  FrontendSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref FrontendSubnet
      RouteTableId: !Ref FrontendRouteTable

[AWS CloudFront] 基礎

CloudFrontとはプログラミング言語やテキストファイルを使用してAWSリソースを自動で構築するサービス
具体的には、リソースの設定、プロビジョニングをコード化したテンプレートを作成できる
Infrastructure as code
EC2やELBといったAWSリソースの環境構築を設定ファイルを元に自動化できる
テンプレートに基づき、各リソースが起動

### cloudFormationのメリット
一度テンプレートを作成すれば、同じ構成を再現できる
ベストプラクティスが盛り込まれたテンプレートが使える
起動時にパラメータを渡せる
利用料金は無料

### cloudformationの使い方
– AWSマネジメントコンソールにあるAWS CloudFormation Designerを使う方法
– AWSマネジメントコンソールからJSON/YAML形式でテキストファイルでテンプレート作成する方法

JSON/YAML -> Framework -> AWSサービスの設定

テンプレートとはJSON形式、YAML形式で作成する
テンプレートはリージョンごとに条件が異なる
プロビジョニングされるリソースの集合をスタックと呼ぶ
スタック単位でリソース管理が可能 スタック破棄を実行すると、リソースを破棄することが可能
(1)クイックスタート、(2)サンプルコード&テンプレート、(3)独自テンプレート作成 などがある
クイックスタートはベストプラクティス
テンプレートは色々ある e.g.サーバレスアプリケーションモデルなど
リソース間の依存関係はCloudFormationが自動判別する

### Resourceとは
EC2、ELB、RDSなど起動するサービスを指定
### Parametersとは
スタック構築時に値を定義できる
Type, Default, NoEcho, AllowValues, AllowdPattern, MaxLength, MinLength, MaxValue, MinValue, MinValue, Description, ConstraintDescription
パラメータの値はテンプレートの中で”ref”により参照
### Function
パラメータの参照やMapの山椒はFunctionを利用する
### 擬似パラメータ(Pseudo Parameter) ※予め定義
– AWS::Region
– AWS::StackId
– AWS::StackName
– AWS::AccountId
– AWS::NotificationARNs
– AWS::NoValue
### Mapping
キーと値のマッピングテーブルを管理できる
Functionの”Find::InMap”を使って値を取得
### Condition
条件名と成立条件を列挙
### Outputs
スタック構築後に取得・表示したい情報の定義

テンプレート作成ツールにCloudFormerがある
CloudFormation Designer

テンプレートとスタックの設計

### デメリット
部分的な修正でもテンプレートを修正しなければならない
-> コンソールで修正した方が操作は楽

運用者にcloudformationの知識が求めらる
部分的な修正でもテンプレートを修正しなければならない為、新規に機能追加などがあるサービスの運用としてはややハードルが高いか
EOLが決まっているサービス、ほぼ改修がないようなサービスで、インフラ構成を変えない場合はcloudformationで導入するのもありか

[SwiftUI] 確認ダイアログの表示

struct ContentView: View {
    @State private var isShowingDialog = false
    
    var body: some View {
        Button(action: {
            isShowingDialog = true
        }) {
            Label("削除ボタン", systemImage: "trash")
        }.confirmationDialog("注意!", isPresented: $isShowingDialog) {
            Button("削除する", role: .destructive){
                destructiveAction()
            }
            Button("キャンセル", role: .cancel){
                cancelAction()
            }
        } message: {
            Text("削除すると戻せません")
        }
    }
    func destructiveAction(){
        print("削除が選ばれた")
    }
    
    func cancelAction(){
        print("キャンセルが選ばれた")
    }
}

### 確認ダイアログのタイトル表示

    var body: some View {
        Button(action: {
            isShowingDialog = true
        }) {
            Label("削除ボタン", systemImage: "trash")
        }.confirmationDialog("注意!", isPresented: $isShowingDialog,
                             titleVisibility: .visible) {
            Button("選択A"){
            }
            Button("選択B"){
            }
            Button("削除する", role: .destructive){
                destructiveAction()
            }
            Button("キャンセル", role: .cancel){
                cancelAction()
            }
        } message: {
            Text("メッセージ、メッセージ、メッセージ、メッセージ") +
            Text("メッセージ、メッセージ、メッセージ、メッセージ")
        }
    }

buttonのactionの箇所は何も記載がないが、ここに実行文を書くのね。

[SwiftUI] アラート文

struct ContentView: View {
    @State var isError: Bool = false
    
    var body: some View {
        Button(action:{
            isError = true
        }){
            Text("Alertテスト")
        }.alert(isPresented: $isError){
            Alert(title: Text("タイトル"), message: Text("メッセージ文"),
                  dismissButton: .default(Text("OK"), action: {}))
        }
    }
}

OKとキャンセルがあるアラート

struct ContentView: View {
    @State var isError: Bool = false
    
    var body: some View {
        Button(action:{
            isError = true
        }){
            Text("Alertテスト")
        }.alert(isPresented: $isError){
            Alert(title: Text("タイトル"), message: Text("メッセージ文"),
                  primaryButton: .default(Text("OK"), action: {
                okAction()
            }),
                  secondaryButton: .cancel(Text("キャンセル"), action:{})
            )}
    }
}
func okAction(){
    print("OKボタンが選ばれた")
}

primaryButtonとsecondaryButtonを指定する

struct ContentView: View {
    @State var isError: Bool = false
    
    var body: some View {
        Button(action:{
            isError = true
        }){
            Text("Alertテスト")
        }.alert(isPresented: $isError){
            Alert(title: Text("タイトル"), message: Text("メッセージ文"),
                  primaryButton: .destructive(Text("削除する"), action: {}),
                  secondaryButton: .cancel(Text("キャンセル"), action:{})
            )}
    }
}

やべーな、swiftかなり舐めてたわ…orz

[SwiftUI] テキストエディタでテキストの読み書き

入力編集ができるテキストエディタを作る

    @State private var theText: String = """
    春はあけぼの。やうやう白くなりゆく山ぎは、すこしあかりて、紫だちたる
    雲のほそくたなびきたる。
    // 省略
    さらでもいと寒きに、火など急ぎおこして、炭もて渡るもいとつきづきし。
    昼になりて、ぬるくゆるびもていけば、火桶の火も白き灰がちになりてわろし。
    """
    var body: some View {
        TextEditor(text: $theText)
            .lineSpacing(10)
            .border(Color.gray)
            .padding()
    }

テキストエディタ

    @State var theText: String = ""
    
    var body: some View {
        NavigationView {
            TextEditor(text: $theText)
                .lineSpacing(10)
                .border(Color.gray)
                .padding([.leading, .bottom, .trailing])
                .navigationTitle("メモ")
        }
    }

保存・読み込みができるようにする
L ユーザが作って読み書きするファイルはDocumentsフォルダ内に保存する
  L DocumentsフォルダまでのURLはFileManager.defaultでfileManagerオブジェクトを作り、fileManager.url()で取得する
  L appendingPathComponent(fileName)で保存ファイル名を追加したURLを作り、そのURLをdocURL(fileName)の値として返す

import SwiftUI

func saveText(_ textData:String, _ fileName:String){
    guard let url = docURL(fileName) else {
        return
    }
    
    do {
        let path = url.path
        try textData.write(toFile: path, atomically: true, encoding: .utf8)
    } catch let error as NSError {
        print(error)
    }
}

func docURL(_ fileName:String) -> URL? {
    let fileManager = FileManager.default
    do {
        let docsUrl = try fileManager.url(
            for: .documentDirectory,
            in: .userDomainMask,
               appropriateFor: nil,
               create: false)
        let url = docsUrl.appendingPathComponent(fileName)
        return url
    } catch {
        return nil
    }
}

func loadText(_ fileName:String) -> String? {
    
    guard let url = docURL(fileName) else {
        return nil
    }
    
    do {
        let textData = try String(contentsOf: url, encoding: .utf8)
        return textData
    } catch {
        return nil
    }
}

struct ContentView: View {
    @FocusState var isInputActive: Bool
    @State var theText: String = ""
    
    var body: some View {
        NavigationView {
            TextEditor(text: $theText)
                .lineSpacing(10)
                .border(Color.gray)
                .padding([.leading, .bottom, .trailing])
                .navigationTitle("メモ")
                .focused($isInputActive)
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing){
                        Button("読み込む"){
                            if let data = loadText("sample.txt"){
                                theText = data
                            }
                        }
                    }
                    ToolbarItem(placement: .navigationBarTrailing){
                        Button("保存"){
                            saveText(theText, "sample.txt")
                        }
                    }
                }
        }
    }
}

### do-try-catchの例外処理

enum KeyError: Error {
    case uniqueError
    case lengthError
}

func keyMaker(_ key1:String, _ key2:String) throws -> String {
    guard key1 != key2 else {
        throw KeyError.uniqueError
    }
    guard (5...10).contains(key1.count)&&(5...10).contains(key2.count) else {
        throw KeyError.lengthError
    }
    let result = (key1 + key2).shuffled()
    return String(result)
}

func testKeyMake1(_ key1:String, _ key2:String){
    do {
        let result = try keyMaker(key1, key2)
        print(result)
    } catch {
        print("エラー")
    }
}

testKeyMake1("Swift", "1234567")
testKeyMake1("Swift", "Swift")
testKeyMake1("Swift", "UI")

func testKeyMake2(_ key1:String, _ key2:String){
    do {
        let result = try keyMaker(key1, key2)
        print(result)
    } catch KeyError.uniqueError {
        print("2つのキーが同じエラー")
    } catch KeyError.lengthError {
        print("文字数エラー")
    } catch {
        print("エラー")
    }
}
testKeyMake2("Swift", "1234567")
testKeyMake2("Swift", "Swift")
testKeyMake2("Swift", "UI")

これは半端ないわ
凄い

[SwiftUI] テキストフィールドの入力

テキストフィールドに入力できるのはStringデータだけなので、型変換をする必要がある
オプショナルバリュー、オプショナルバインディングを学ぶ

TextField, textFieldStyle, isEmpty, keyboardType, Group, @FocusState, fucused, toolbar, ToolbarItemGroup, guard let-else, if let-else, !, ??, nil

### テキストフィールドを作る
入力した名前を表示する
 L TextFieldで作成する

    @State var name: String = ""
        
    var body: some View {
        TextField("お名前は?", text: $name)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .frame(width: 250)
        
        if (!name.isEmpty){
            Text("\(name)さん、こんにちは!")
        }
    }

キーボードの種類を指定
L keyboardType(.numberPad)のように指定する
L 複数のビューを一纏めにする場合は Groupで囲む
  L guard let-elseで整数に変換できたら変換後の値をnumに代入、変換できなかったらfalse

struct ContentView: View {
    @State var kosu:String = ""
    let tanka:Double = 250
    let tax:Double = 1.1
        
    var body: some View {
        VStack(alignment: .leading){
            
            HStack {
                Text("個数:").padding(.horizontal, 0)
                TextField("0", text: $kosu)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .keyboardType(.numberPad)
                    .frame(width: 100)
            }
            .font(.title)
            .frame(width: 200)
            
            Group {
                if kosuCheck(min: 1, max: 10){
                    Text("\(price())円です。")
                        .font(.title)
                } else {
                    Text("個数は1〜10個を入れてください。")
                        .foregroundColor(.red)
                        .font(.headline)
                }
            }.frame(width: 300, height: 30)
        }
    }
    
    func kosuCheck(min:Int, max:Int) -> Bool {
        guard let num = Int(kosu) else {
            return false
        }
        return (num>=min && num <= max)
    }
    
    func price() -> Int {
        if let num = Double(kosu){
            let result = Int(tanka * num * tax)
            return result
        } else {
            return -1
        }
    }
}

Doneボタンの追加
L focusが終わるとキーボードが下がる

@FocusState var isInputActive: Bool
    @State var kosu:String = ""
    let tanka:Double = 250
    let tax:Double = 1.1
        
    var body: some View {
        VStack(alignment: .leading){
            
            HStack {
                Text("個数:").padding(.horizontal, 0)
                TextField("0", text: $kosu)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .keyboardType(.numberPad)
                    .frame(width: 100)
                    .focused($isInputActive)
            }
            .font(.title)
            .frame(width: 200)
            
            Group {
                if kosuCheck(min: 1, max: 10){
                    Text("\(price())円です。")
                        .font(.title)
                } else {
                    Text("個数は1〜10個を入れてください。")
                        .foregroundColor(.red)
                        .font(.headline)
                }
            }.frame(width: 300, height: 30)
        }
        .toolbar {
            ToolbarItemGroup(placement: .keyboard){
                Spacer()
                Button("Done"){
                    isInputActive = false
                }
            }
        }
    }

### オプショナルバインディング
nilかもしれないオプショナルバリュー

let nums:[Int] = []
let lastNum = nums.last
let ans = lastNum * 2

var num:Int
num = 5
num = nil

代入できる変数を作る

var num:Int?
num = 5
num = nil

??演算子を使う

let nums:[Int] = [1, 2, 3]
let lastNum = nums.last ?? 0
let ans = lastNum * 2
print(ans)

### guard let-elseを使ったオプショナルバインディング
関数を実行する前にオプショナルバリューをチェックするオプショナルバインディング構文
L Int(kosu)がnilならfalseを戻す
  L if let elseでも同様にオプショナルバインディングを行う

    func kosuCheck(min:Int, max:Int) -> Bool {
        guard let num = Int(kosu) else {
            return false
        }
        return (num>=min && num <= max)
    }

なるほど、中々深いな
バリデーションでチェックしてたから意識したことなかったけど、Swiftの場合はnilをチェックする場合はこう書くのね。