2021/12/18

zmqの通信の認証と暗号化

目的


ZMQで認証&通信暗号化をしてみます。言語はPythonを使います。

コード


REQ-REPで試してみます。Public keyとSecret keyを作成するコード、サーバーのコード、クライアントのコードが必要になります。

キー作成


次のgen_keys.pyを作成し、pythonで実行します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import os
import zmq
import zmq.auth

def main():
    os.makedirs("cert", exist_ok=True)
    server_public_file, server_secret_file = zmq.auth.create_certificates("cert", "server")
    client_public_file, client_secret_file = zmq.auth.create_certificates("cert", "client")

if __name__ == '__main__':
    main()
すると、certディレクトリに
client.key
client.key_secret
server.key
server.key_secret
の4つのファイルが作成されます。

サーバー


サーバーのコードは以下の通り。サーバー側に認証機能をつけます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import zmq
import zmq.auth
from zmq.auth.thread import ThreadAuthenticator

def main():
    ctx = zmq.Context.instance()
    auth = ThreadAuthenticator(ctx)
    auth.start()
    auth.configure_curve(location="cert") # Need only public keys
    server = ctx.socket(zmq.REP)
    server.curve_publickey, server.curve_secretkey = zmq.auth.load_certificate("cert/server.key_secret")
    server.curve_server = True
    server.bind('tcp://*:9000')

    while True:
        msg = server.recv_pyobj()
        print("server:", msg)
        server.send_pyobj("123")

if __name__ == '__main__':
    main()
無限ループをCtrl+Cで止める前提なので入れていませんが、普通はauth.stop()で認証用スレッドを終了しましょう。

クライアント


クライアントのコードは以下の通り。サーバーのpublic keyが必要です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import zmq
import zmq.auth

def main():
    ctx = zmq.Context.instance()
    client = ctx.socket(zmq.REQ)
    client.curve_publickey, client.curve_secretkey = zmq.auth.load_certificate("cert/client.key_secret")
    client.curve_serverkey, _ = zmq.auth.load_certificate("cert/server.key")
    client.connect('tcp://127.0.0.1:9000')

    client.send_pyobj("abc")
    msg = client.recv_pyobj()
    print("client:", msg)

if __name__ == '__main__':
    main()

サーバーを実行し、その後、クライアントを実行すると、サーバー側に

server: abc
クライアント側に
client: 123
と表示されます。

クライアント側で指定するサーバーのPublic keyを間違えるとサーバーには接続できません。 また、Wiresharkで通信内容を見た限りでは、abcや123は平文では通信されていませんでした。 一方、認証関連のコード(authやcurve関連)を削除して実行すると、abcや123を平文で読むことができました。

参考


https://github.com/zeromq/pyzmq/blob/main/examples/security/generate_certificates.py
https://github.com/zeromq/pyzmq/blob/main/examples/security/ironhouse.py

2021/12/11

bashでN進数表記

整数計算に限りますが、bashで$(( ))による計算をするときに# (ASCIIコードで0x23、シャープ、井桁、番号記号、ナンバーサイン、ハッシュ、どう呼べばよいのか?)を使うとN進数で記載できます。計算結果の表示は10進数になります。

2進数

$ echo $((2#1110))
14

3進数

$ echo $((3#201))
19

4進数

$ echo $((4#123))
27

5進数

$ echo $((5#234))
69

8進数

$ echo $((8#17))
15

10進数

$ echo $((10#17))
17

16進数

$ echo $((16#fe))
254

20進数

$ echo $((20#ji))
398

40進数

$ echo $((40#D))
39

50進数

$ echo $((50#N))
49

60進数

$ echo $((60#X))
59

64進数

$ echo $((64#Z))
61
$ echo $((64#@))
62
$ echo $((64#_))
63

基底の最大値は64で、それより値が大きいと

$ echo $((65#1))
bash: 65#1: 無効な基底の数値です (エラーのあるトークンは "65#1")
とエラーになります。

2021/12/07

OzoneのデータにC++でアクセス

Apache Ozone上にあるデータにC++でアクセスしてみます。Ozoneの準備は、「Ozoneを試す」を参照ください。

ライブラリのビルド


https://docs.aws.amazon.com/sdk-for-cpp/v1/developer-guide/setup-linux.htmlを参考にビルドしていきます。

まず、ダウンロードします。

$ git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp.git
この日の時点では、1.4GBありました。大きいですね。

ビルドにはUbuntuでは libcurl4-openssl-dev, libssl-dev, uuid-dev, zlib1g-dev をインストールしておく必要があるようです。

ビルドします。

$ cd aws-sdk-cpp
$ mkdir sdk_build
$ cd sdk_build
$ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../sdk -DBUILD_ONLY="s3" ..
$ make
$ make install

Ozoneにアクセス


https://docs.aws.amazon.com/sdk-for-cpp/v1/developer-guide/build-cmake.htmlなどを参考に、ビルドしたライブラリを使ってOzoneにアクセスしてみます。

すべてのBucketのすべてのObjectを読み込んで標準出力に出力するソースコードmain.cppは次の通りです。 なお、テキストデータの表示のためヌル終端していますが、単に読み込むだけであれば不要です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <aws/core/Aws.h>
#include <aws/core/utils/logging/LogLevel.h>
#include <aws/s3/S3Client.h>
#include <aws/s3/model/ListObjectsRequest.h>
#include <aws/s3/model/GetObjectRequest.h>
#include <iostream>

class Initializer{
public:
  Initializer(){
    options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Debug;
    Aws::InitAPI(options);
  }
  ~Initializer(){
    Aws::ShutdownAPI(options);
  }
private:
  Aws::SDKOptions options;
};

int main(void){
  Initializer initializer;
  Aws::Client::ClientConfiguration config;
  config.endpointOverride = "http://localhost:9878";

  // client cannot access objects if useVirtualAddressing is true
  Aws::S3::S3Client client(config, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, false/*useVirtualAddressing*/);
  Aws::S3::Model::ListBucketsOutcome buckets = client.ListBuckets();
  if(!buckets.IsSuccess()){
    std::cout << "Fail to get buckets" << std::endl;
    return 1;
  }
  Aws::S3::Model::ListBucketsResult listBucketsResult = buckets.GetResult();
  for(const Aws::S3::Model::Bucket &bucket : listBucketsResult.GetBuckets()){
    std::cout << "Bucket name: " << bucket.GetName() << std::endl;

    // Get objects
    Aws::S3::Model::ListObjectsRequest request;
    request.WithBucket(bucket.GetName());
    Aws::S3::Model::ListObjectsOutcome listObjectsOutcome = client.ListObjects(request);
    if(!listObjectsOutcome.IsSuccess()){
      std::cout << "Fail to get objects" << std::endl;
      continue;
    }
    Aws::S3::Model::ListObjectsResult listObjectsResult = listObjectsOutcome.GetResult();
    std::cout << "Bucket name: " << listObjectsResult.GetName() << std::endl;
    std::cout << "Max keys: " << listObjectsResult.GetMaxKeys() << std::endl;
    const Aws::Vector<Aws::S3::Model::Object> &objects = listObjectsResult.GetContents();
    std::cout << "# of objects: " << objects.size() << std::endl;
    for(const Aws::S3::Model::Object& object : objects){
      // Get the attributes of the object
      std::cout << "Object key: " << object.GetKey() << std::endl;
      long long size = object.GetSize();
      std::cout << "Object value size: " << size << std::endl;

      // Get the value of the object
      Aws::S3::Model::GetObjectRequest objectRequest;
      objectRequest.SetBucket(listObjectsResult.GetName());
      objectRequest.SetKey(object.GetKey());
      Aws::S3::Model::GetObjectOutcome getObjectOutcome = client.GetObject(objectRequest);
      if(!getObjectOutcome.IsSuccess()){
        std::cout << "Fail to get content" << std::endl;
        continue;
      }
      Aws::S3::Model::GetObjectResult getObjectResult = getObjectOutcome.GetResultWithOwnership();
      long long contentLength = getObjectResult.GetContentLength();
      std::cout << "Object value size: " << contentLength << std::endl;
      Aws::IOStream& stream = getObjectResult.GetBody();
      std::vector<char> buff(contentLength+1); // +1 for null terminator
      stream.read(&buff[0], buff.size());
      std::replace(buff.begin(), buff.end(), '\n', '!');
      std::cout << "Object value: " << &buff[0] << std::endl;
    }
  }
  return 0;
}
CMakeLists.txtは次の通りです。
cmake_minimum_required(VERSION 3.3)
set(CMAKE_CXX_STANDARD 11)
project(s3)

add_executable(s3 main.cpp)
target_include_directories(s3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../aws-sdk-cpp/sdk/include)
target_link_libraries(s3 ${CMAKE_CURRENT_SOURCE_DIR}/../aws-sdk-cpp/sdk/lib/libaws-cpp-sdk-s3.so
  ${CMAKE_CURRENT_SOURCE_DIR}/../aws-sdk-cpp/sdk/lib/libaws-cpp-sdk-core.so)
最初に準備したaws-sdk-cppが、このファイルのあるディレクトリの親ディレクトリ内にあることを前提としています。
┬ aws-sdk-cpp
└ example
   ├CMakeLists.txt
   └main.cpp

今、exampleディレクトリにいるとして、

$ mkdir build
$ cd build
$ cmake ..
$ make
でビルドし、作成されるs3コマンドを実行すると、
$ ./s3
Bucket name: bucket1
Bucket name: bucket1
Max keys: 1000
# of objects: 1
Object key: test.txt
Object value size: 10
Object value size: 10
Object value: test test!
と表示されます。正しく読み込めました。

参考


https://qiita.com/kai_kou/items/095e409539fbe77f0d59

2021/12/04

Ozoneを試す

Apache Ozoneを試してみます。Dockerを事前に使えるようにしておいてください。

準備


Ozoneの立ち上げは https://ozone.apache.org/docs/1.1.0/start/startfromdockerhub.html に従って進めます。

といっても、

$ docker run -p 9878:9878 -p 9876:9876 apache/ozone
を実行するだけです。

操作にはawsコマンドを使用しますので、Ubuntuの場合であれば、

$ apt install awscli
インストールします。また、設定ファイルを作成する必要があるため、例えば、
$ aws configure
AWS Access Key ID [None]: default
AWS Secret Access Key [None]: default
Default region name [None]: default
Default output format [None]: text
のようにします。今dockerで立ち上げたOzoneは ozone.security.enabled=false となっているため、適当な設定でもアクセスできるようです。今は試したいだけなので、このままで進めます。

バケット作成


最初にデータを保管するためのバケットを作る必要があります。バケットの説明は https://www.ipswitch.com/jp/blog/understanding-how-aws-s3-buckets-work が参考になりました。

以下のコマンドでbucket1という名前のバケットを作成できます。

$ aws s3api --endpoint http://localhost:9878/ create-bucket --bucket=bucket1

オブジェクトの格納


データをバケットに格納します。まずコピーするファイル test.txt を作ります。
$ echo "test test" > test.txt
次に、このファイルをバケット内にコピーします。
$ aws s3 --endpoint http://localhost:9878 cp test.txt  s3://bucket1/test.txt
upload failed: ./test.txt to s3://bucket1/test.txt An error occurred (500) when calling the PutObject operation (reached max retries: 4): Internal Server Error
すると、エラーが発生します。今立ち上げているOzoneがシングルコンテナであるため、エラーが発生するようです。そこで、
$ aws s3 --endpoint http://localhost:9878 cp --storage-class REDUCED_REDUNDANCY test.txt  s3://bucket1/test.txt
upload: ./test.txt to s3://bucket1/test.txt
とすると成功します。
$ aws s3 --endpoint http://localhost:9878 ls s3://bucket1/test.txt
2021-12-04 22:20:53         10 test.txt
でファイルが存在することが確認できました。また、
$ aws s3 --endpoint http://localhost:9878 cp s3://bucket1/test.txt -
test test
でファイルの中身を表示できます。出力ファイル名を-にすることで標準出力に流してくれるようです。 これで正しくコピーできていることが確認できました。

Pythonから利用


boto3というライブラリを用いるとPythonからアクセスできます。
$ pip3 install boto3
でインストールします。

次のコード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import boto3

s3 = boto3.resource('s3', endpoint_url="http://localhost:9878")
for b in s3.buckets.all():
    print(" 1:", b)
    print(" 2:", b.name)
    for obj in b.objects.all():
        print(" 3:", obj.key)
        print(" 4:", obj.size)
        print(" 5:", obj.get()["Body"])
        print(" 6:", obj.get()["Body"].read())
        body = obj.get()["Body"]
        print(" 7:", body.read().decode("utf-8"))
        print(" 8:", body.read())
    print(" 9:", b.Object("test.txt").get()["Body"].read())
print("10:", s3.Object("bucket1", "test.txt").get()["Body"].read())
をpython3で実行すると、
 1: s3.Bucket(name='bucket1')
 2: bucket1
 3: test.txt
 4: 10
 5: <botocore.response.StreamingBody object at 0x7f9ae2a71d00>
 6: b'test test\n'
 7: test test

 8: b''
 9: b'test test\n'
10: b'test test\n'
という結果が出力されます。キーをリストアップしてオブジェクトの中身を表示することも、キーを指定してオブジェクトの中身を表示することもできます。 なお、取得したbodyに対して2回readすると、1回目で読み終わっているため、2回目は空が返ってきます。

参考


https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html
https://botocore.amazonaws.com/v1/documentation/api/latest/reference/response.html#botocore.response.StreamingBody
https://medium.com/towards-data-engineering/get-keys-inside-an-s3-bucket-at-the-subfolder-level-7be42d858372