2022年3月11日金曜日

Gitlabでバックアップファイルをプロキシ配下でs3にアップロードする方法

Gitlabでバックアップファイルをプロキシ配下でs3にアップロードする方法

概要

過去にバックアップファイルをs3にアップロードする方法を紹介しました
今回はプロキシ配下でアップロードする方法を紹介します
内部的には fog を使っているので fog でプロキシを設定する感じになります

環境

  • Gitlab 14.5.4

gitlab.rb

connection_options を指定します

gitlab_rails['backup_upload_connection'] = {
  'provider' => 'AWS',
  'region' => 'us-east-2',
  'aws_access_key_id' => 'AKIxxxxxxxxxxxxxxxxxxx',
  'aws_secret_access_key' => 'xxxxxxxxxxxxxxxxxxxxx',
  'use_iam_profile' => false,
  'endpoint' => 'https://s3.us-east-2.amazonaws.com',
  'aws_signature_version' => 4,
  'connection_options' => {
    'proxy' => 'http://192.168.100.1:3128',
  },
  'enable_signature_v4_streaming' => false,
  'path_style' => true,
}
gitlab_rails['backup_upload_remote_directory'] = 'test-bucket'

EXCON_DEBUG=true にすると接続時の詳細なログが表示されるようになります

注意点

「InvalidBucketName」が出る場合は path_style の値を調整してください

「STREAMING-AWS4-HMAC-SHA256-PAYLOAD is not supported」が出る場合は enable_signature_v4_streaming を false に設定してください

参考サイト

2022年3月10日木曜日

jQuery でイメージファイルを base64 エンコードデータに変換する方法

jQuery でイメージファイルを base64 エンコードデータに変換する方法

概要

バイナリファイルを base64 エンコードファイルに変換する方法を紹介します
サーバサイドでデータベースにテキストデータとして保存する場合に役に立ちます

FileReader API の readAsDataURL というメソッドを使います

環境

  • macOS 11.6.4
  • Ruby 3.0.3p157
  • sinatra 2.2.0

サーバ側実装

  • bundle init
  • vim Gemfile
gem "sinatra"
gem "thin"
  • bundle install
  • mkdir views public upload
  • vim app.rb
# coding: utf-8
require 'sinatra'

class TestUpload < Sinatra::Base
  get '/' do
    erb :index
  end

  post '/upload' do
    base64str = params['base64str']
    content_type :json, :charset => 'utf-8'
    {"result": base64str}.to_json
  end
end

サーバ側はデータをフォームデータとして取得することを想定しています
今回は受け取ったデータの保存などはせずそのままクライアントに返却しています

クライアント側実装

  • vim views/index.erb
<!DOCTYPE HTML>
<html>
  <head>
  <meta charset="utf-8">
  <title>Upload Test</title>
  </head>
<body>
<input id="upload_file" type="file" name="file">
<button id="upload" type="button">Upload</button>
<div id="result"></div>
<img id="uploaded_img" src=""></img>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script>
$(function () {
  $('#upload').click(function() {
    let file = $('#upload_file').prop('files')[0];
    // 3MiB
    let max = 3 * 1024 * 1024
    if (file.size > max) {
      $('#result').text("File size must be 3mib or less.")
    } else {
      let reader = new FileReader();
      reader.addEventListener("load", function (){
	let fd = new FormData();
        fd.append("base64str", reader.result);
        $.ajax({
          url:'/upload',
          type:'post',
          data: fd,
          processData: false,
          contentType: false,
          cache: false,
        }).done(function (data) {
          $('#result').text("uploaded.")
	  $('#uploaded_img').attr('src', data.result)
        }).fail(function() {
          $('#result').text("upload failed.")
        });
      }, false);
      if (file) {
        reader.readAsDataURL(file);
      }
    }
  });
});
</script>
</body> 
</html>

FileReader を生成し readAsDataURL の引数にファイルオブジェクトを渡します
そして FileReader オブジェクトに対して load イベントリスナーを設定します
すると base64 化が完了したときに load イベントがコールされその中で reader.result のようにすることで base64 化された画像の文字列情報が取得できます

送信するのはフォームデータにしています
理由はいくつかありますが JSON にしてしまうと base64 化された文字列をサーバ側でパースする際にパースエラーが発生するためフォームデータにしています

最後に

サーバ側にファイルデータを送信する際にバイナリをそのまま送信してもいいのですがクライアント側で base64 にしてあげるとサーバ側で頑張る必要が少なくなります

ちなみに ruby でも Base64.strict_encode64 とするだけで base64 化できるのでできるのはサーバサイドでもクライアントサイドでもどちらでもできます

参考サイト

2022年3月9日水曜日

jQuery を使ってファイルをアップロードする方法

jQuery を使ってファイルをアップロードする方法

概要

前回はライブラリを使った方法を紹介しました
今回は jQuery のみを使ってファイルをアップロードする方法を紹介します

環境

  • macOS 11.6.4
  • Ruby 3.0.3p157
  • sinatra 2.2.0

サーバ側実装

サーバ側のコードは前回と同じです

クライアント側実装

  • vim views/index.erb
<!DOCTYPE HTML>
<html>
  <head>
  <meta charset="utf-8">
  <title>Upload Test</title>
  </head>
<body>
<input id="upload_file" type="file" name="file">
<button id="upload" type="button">Upload</button>
<div id="result"></div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script>
$(function () {
  $('#upload').click(function() {
    let fd = new FormData();
    fd.append("file", $('#upload_file').prop('files')[0]);
    $.ajax({
      url:'/upload',
      type:'post',
      data: fd,
      processData: false,
      contentType: false,
      cache: false,
    }).done(function (data) {
      $('#result').text("uploaded.")
    }).fail(function() {
      $('#result').text("upload failed.")
    });
  });
});
</script>
</body> 
</html>

少し解説

jQuery でファイルを送信するには FormData + ajax を使います
ファイルは複数指定されることを想定していますが今回は1ファイルのみ指定可能にしているので先頭のファイルのみを送信します ($('#upload_file').prop('files')[0])

prop のあとの files は必ず「files」でなければなりません

あとはサーバ側でファイルを保存すれば OK です

最後に

ライブラリを使うとプログレスバーやアップロード後のコールバックメソッドが予め定義されているので便利なのは便利なのですが使わない機能もあります

本当に簡単なフォームを作成したいだけだればこれくらいでもいいかもしれません

参考サイト

2022年3月8日火曜日

jQuery-File-Upload を使ってみた

jQuery-File-Upload を使ってみた

概要

jQuery-File-Upload は jQuery を使って簡単にファイルアップロード用のフォームが実装できるライブラリです
今回は簡単な使い方を紹介します

なおこのリポジトリはすでにアーカイブ状態になっているので使用する際はご注意ください

環境

  • macOS 11.6.4
  • Ruby 3.0.3p157
  • sinatra 2.2.0

サーバ側実装

  • bundle init
  • vim Gemfile
gem "sinatra"
gem "thin"
  • bundle install
  • mkdir views public upload
  • vim app.rb
# coding: utf-8
require 'sinatra'

class TestUpload < Sinatra::Base
  get '/' do
    erb :index
  end

  post '/upload' do
    if params[:file]
      save_path = "./upload/#{params[:file][:filename]}"
      File.open(save_path, 'wb') do |f|
        f.write params[:file][:tempfile].read
        @msg = params[:file][:filename].force_encoding("utf-8")
      end
    else
      @msg = "Upload Error..."
    end
    content_type :json, :charset => 'utf-8'
    {'filename': @msg}.to_json
  end
end

クライアント側実装

  • vim views/index.erb
<!DOCTYPE HTML>
<html>
  <head>
  <meta charset="utf-8">
  <title>Upload Test</title>
  </head>
<body>
<input id="fileupload" type="file" name="file" data-url="/upload">
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="jQuery-File-Upload/js/vendor/jquery.ui.widget.js"></script>
<script src="jQuery-File-Upload/js/jquery.iframe-transport.js"></script>
<script src="jQuery-File-Upload/js/jquery.fileupload.js"></script>
<script>
$(function () {
  $('#fileupload').fileupload({
    dataType: 'json',
    add: function (e, data) {
      data.context = $('<button></button>').text('Upload')
      .appendTo(document.body)
      .click(function () {
        data.context = $('<p></p>').text('Uploading...').replaceAll($(this));
        data.submit();
      });
    },
    done: function (e, data) {
      data.context.text(data.result.filename);
    }
  });
});
</script>
</body> 
</html>

jQuery-File-Upload のインストール

CDN などですべてのファイルが公開されているわけではないので手元に持ってきてから使用します

動作確認

フォーム画面が表示されてファイルが upload ディレクトリ配下に作成されれば OK です

少し解説

$('#fileupload').fileupload を呼び出すだけで必要なフォームを作成してくれます
今回はボタンを押したらアップロードしたかったので add コールバックメソッドを実装していますがなくても動作します

レスポンスは JSON 形式を想定しているので Sinatra から返却されるレスポンスは application/json にする必要があります

Sinatra 側ではファイルをローカルに保存していますがここは自由に変更して OK です

Sinatra の public ファイルは public ディレクトリ配下に配置する必要があるので git clone したライブラリは public 配下に配置しています

最後に

スタイルなど当てることもできるようです
リポジトリはすでにリードオンリーになっているのでくれぐれも使用する際は注意しましょう

参考サイト

2022年3月7日月曜日

git でローカルにあるブランチをすべて削除する方法

git でローカルにあるブランチをすべて削除する方法

コマンド

  • git branch | grep -v \* | xargs git branch -D

マージ前のブランチもすべて削除されます
マージ前のブランチを残したい場合は git branch --merge オプションで実行します

2022年3月4日金曜日

botocore でプロキシを設定する方法

botocore でプロキシを設定する方法

概要

boto3 を使って s3 API にプロキシ経由でアクセスする方法を紹介します

環境

  • Ubuntu 18.04
  • Python 3.10.2
  • boto3 1.12.11

サンプルコード

import boto3
from botocore.config import Config


class TestClient():

    def __init__(self):
        proxies = {
            'http': 'http://192.168.100.1:3128',
            'https': 'http://192.168.100.1:3128'
        }
        config = Config(
            region_name='us-east-1',
            proxies=proxies
        )
        self.client = boto3.client(
            's3',
            aws_access_key_id='AKIxxxxxxxxxxx',
            aws_secret_access_key='xxxxxxxxxxx',
            config=config,
        )

    def list_buckets(self):
        return self.client.list_buckets()


if __name__ == '__main__':
    cli = TestClient()
    print(cli.list_buckets())

botocore.config を使って設定できます
各種設定は環境変数やコンフィグファイル化してください

参考サイト

2022年3月3日木曜日

pyjwt で jwk を使って認証情報を取得しそこから jwt を復号化する方法

pyjwt で jwk を使って認証情報を取得しそこから jwt を復号化する方法

概要

jwk は鍵を公開鍵管理する API で JSON 形式で取得できます
今回は jwk で取得した鍵情報を使用してそこから jwt を復号化する方法を紹介します
なお暗号化方式は ES512 で楕円曲線演算方法は P-521 の場合になります

鍵などの情報は適当に記載しています

環境

  • macOS 11.6.4
  • Python 3.10.2
  • pyjwt 2.3.0

サンプルコード

import jwt
import json

from jwt.algorithms import ECAlgorithm
from cryptography.hazmat.primitives import serialization

jwks = '{"keys":[{"alg":"ES512","crv":"P-521","kid":"key_id1","kty":"EC","use":"sig","x":"66_bytes_characters","y":"65_or_66_bytes_characters"}]}'
jwks_dict = json.loads(jwks)
jwt_token = "encripted_json_web_token"

# トークンから kid や alg などのヘッダ情報を取得
header = jwt.get_unverified_header(jwt_token)
# print(header)

# 公開鍵の一覧から kid に一致する jwk を取得
jwk_json = [jwk for jwk in jwks_dict['keys'] if jwk['kid'] == header['kid']]

# 公開鍵情報から暗号化するための pem キーを生成
public_key = ECAlgorithm.from_jwk(jwk_json[0])
pem = public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)

# pem キーを使って jwt トークンを復号化する
decoded_jwt = jwt.decode(jwt_token, pem, algorithms=jwk_json[0]['alg'])
print(decoded_jwt)

軽く説明

まず jwk が公開してる鍵情報の JSON を jwks に設定しています
鍵が複数ある場合もあるので今回は複数の鍵を想定しています

まずトークン情報から jwt.get_unverified_header を使ってヘッダ情報を取得します
ここに暗号化した鍵の kid やアルゴリズムが記載されています

次に kid を元に公開鍵の一覧から復号化するための jwk を取得します
ヘッダに含まれる kid を使って合致する公開鍵を設定します

取得した jwk から PEM キーを生成します
生成するには ECAlgorithm.from_jwk を使います

あとは jwt.decode を使ってトークンをデコードします

pyjwt にパッチを当てる必要がある

実は pyjwt をそのまま使った場合だと動作しない場合があります
jwk で公開している楕円曲線演算方法が P-521 の場合 Y の鍵が 66 bytes である必要があります
しかし実際は 65 or 64 bytes (64bytes である可能性は 0.2%) になる可能性があり pyjwt では 65 or 64 bytes のときを考慮しておりません

なので以下のように条件を変更するパッチを当てる必要があります

  • vim /path/to/venv/site-packages/jwt/algorithms.py
elif curve == "P-521":
    if len(x) == 66 and (len(y) == 66 or len(y) == 65):
        curve_obj = ec.SECP521R1()
    else:
        raise InvalidKeyError("Coords should be 66 bytes for curve P-521")

最後に

jwk は基本的に https 経由で取得することになると思います
jwt も REST などの API 経由のレスポンスで取得することになると思います

pyjwt は修正が必要なので別の jwt ライブラリを使えばパッチを必要はないかもしれません (jose)

参考サイト