Lightsail: CLIENT_UNAUTHORIZED [769]
事象
Lightsailコンソールからのログインでエラー。DistroはUbuntu 18.04LTSを20.04LTSにアップグレードしたもの。
CLIENT_UNAUTHORIZED [769]
原因
新しいバージョンのSSH (8.2) から、SHA-1ハッシュのサポートが標準ではサポートされなくなっているため。 旧バージョンからアップグレードした場合、公開鍵の署名がSHA-1のままになっている。
Future deprecation notice
It is now possible[1] to perform chosen-prefix attacks against the SHA-1 hash algorithm for less than USD$50K. For this reason, we will be disabling the "ssh-rsa" public key signature algorithm that depends on SHA-1 by default in a near-future release.
This algorithm is unfortunately still used widely despite the existence of better alternatives, being the only remaining public key signature algorithm specified by the original SSH RFCs.
調査
Ubuntuの場合、 sshd
のログは、 /var/log/auth.log
にある。
中身を見てみると、以下のログを発見。
Jan 1 02:38:22 ip-172-26-5-22 sshd[1066128]: userauth_pubkey: certificate signature algorithm ssh-rsa: signature algorithm not supported [preauth] Jan 1 02:38:22 ip-172-26-5-22 sshd[1066128]: Connection closed by authenticating user ubuntu 54.240.XXX.XXX port 2190 [preauth]
対応
いったん、SSH-RSAを許可。でも、クラックされるからサポートされなくなったわけで、本来は公開鍵を変えるべき。
# diff sshd_config sshd_config_backup-2021-01-01 125,126d124 < < CASignatureAlgorithms ^ssh-rsa
参考情報
以下のAWS Developer Forumにある事象と同じと思われる。
[https://forums.aws.amazon.com/thread.jspa?threadID=307027:What causes a "CLIENT_UNAUTHORIZED [769]" login error?]
これはHashicorpの例だけど、同じ原因みたい。
Docker + WSL2
Docker + WSL2での開発ってできるの?という疑問を調べてみたメモ。
Dockerのコンテナを動かす部分と、イメージを格納する部分は、WSL2を使うようにできる。WSL2がある状態でDockerをインストールすると、勝手にそれを使うように設定してくれる。
インストール後にWSL2のDistroを見てみると、2つ追加されている。
PS C:\Users\choge> wsl -l -v NAME STATE VERSION * WLinux Running 2 docker-desktop-data Running 2 docker-desktop Running 2
この状態で何らかのコンテナを起動すると、WSL2上にイメージがダウンロードされて、WSL2のKernelでそのイメージが動くみたい。よって、やってることは基本的にはVM立ててその中でDockerを動かしているのと同じ。ただし、スピードはだいぶ速いらしい。
開発方法としては、大きく二つありそうな感じ:
- あくまでも実行環境 (or ビルド環境) としてDockerを使う。
- 開発環境自体としてDockerを使う。
- この場合、OS+Programming言語+Middleware+依存ライブラリなどをイメージにしておいて、そのイメージをもとにしたコンテナでシェルを起動して開発する感じ。
- VS Codeを使っているのであれば、なんかいい感じのオプションで開発できるっぽい。
よって、Javaのアプリの開発環境をDockerでまとめて配布、とかは若干無駄な感じがする。結局IDEとか必要だし。これとかこれも、HelloWorld動かしてるだけだし・・・
NodeJSとかPythonとか、VS Codeで十分開発までできちゃうスクリプト言語の開発にはいいかも。特に pip
の挙動が微妙なPythonにはちょうど良いかも。
参考資料
大体が出来上がったものをコンテナで実行してる感じ。下のアメブロの記事は、VS Codeを使ってコンテナ内で開発までやってる。
Dockerで始める Java EE アプリケーション開発 for JJUG CCC 2017
『VSCodeとDockerでSpring Boot + PostgreSQL開発環境を作る(1)』
以下のはPythonとかの開発
MacでAWSのCodeCommitをcloneできない
事象
git clone
すると、以下のエラーメッセージ。実際には当該レポジトリは存在している。
fatal: repository 'https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/sample/' not found
環境
- OS: macOS High Sierra (10.13.3)
- git: OS Xに付いてるやつ
- AWS: 複数のアカウントあり
- おそらく、複数のIAMユーザーだったり、profileを切り替えたりしても発生する。
原因
OS XのKeychainが勝手に最初に使った認証情報をキャッシュしているため。
対策
/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig
(OS Xのデフォルト) または、/usr/local/git/etc/gitconfig
(brew
で入れたやつ)を編集。
以下をコメントアウトするか、[credential "https://github.com"]
のようにサイトを指定する。
[credential] helper = osxkeychain
参考資料
基本的に以下に書いてあることそのまま。エラーメッセージでググっても出てこないんだよね。
Spring + Vue.jsでページングする
Spring Dataを使うと、Pagingをいい感じにやってくれる。こいつとVue.jsを使って、いい感じにページングをしてみる。
API
SpringのRestController
とPageable
が全てよしなに取り計らってくれる。
@RequestMapping("/api") @RestController public class APIController { @Autowired private ItemRepository repo; @GetMapping("/items") public Page<Item> allItems(Pageable pageable) { return repo.findAll(pageable); } }
これで、以下のようにページングしやすい形でResponseを返してくれる。
{ content: [ ... (省略) ...], pageable: { sort: { sorted: false, unsorted: true }, offset: 200, pageSize: 20, pageNumber: 10, unpaged: false, paged: true }, totalPages: 21, totalElements: 410, last: false, size: 20, numberOfElements: 20, first: false, sort: { sorted: false, unsorted: true }, number: 10 }
英語だけど以下の記事がわかりやすい。HATEOAS 対応しようとしたけど、面倒くさそうなので後回し。
画面
上記のAPIで返ってきた結果を表示する。Vue.js始めたてなので、もっとスマートなやり方はあるかも。
まずはページングするためのComponent。単純に全ページ分を表示する。20ページを超えたあたりからちょっと見づらくなるかも。
個人的にポイントだと思ったのは以下。
RestController
が返すJSONのうち、content
以外をとりあえずpager
というpropsにして渡す。その後の制御は大体これで事足りる。- JSONに含まれるページ番号は0始まりのインデクスだが、
v-for="page in page.totalPages"
でループするpage
は1始まり。 - 各ページがアクティブ(=現在ページ)かどうかを判定するために、インラインで
{'active': page - 1 === pager.number}
としている。 - ページ遷移をするときは、propsで渡された
pager
自身を更新する必要があるので、親Componentとやり取りする必要がある。調べた限りだと以下の方法がありそう。- 親Componentをpropsで子Componentにわたす
$emit
でイベントを発行し、そいつを親Componentで捕まえる (ここの解説)
Vue.component('pager-block', { props: ['pager'], data: {}, computed: {}, methods: { changepage: function(page) { this.$emit('changepage', page); } }, template: ` <nav aria-label="Page navigation example"> <ul class="pagination"> <li class="page-item" :class="{disabled: pager.first}"> <a class="page-link" href="#">Previous</a> </li> <template v-for="page in pager.totalPages"> <li class='page-item' :class="{'active': page - 1 === pager.number}"> <a class="page-link" href="#" :class="page" @click="changepage(page - 1)">{{page}}</a> </li> </template> <li class="page-item" :class="{disabled: pager.last}"> <a class="page-link" href="#">Next</a> </li> </ul> </nav> `, });
親Componentはこんな感じ。
methods
でページ読み込みの処理を作っておいて、mounted
から呼び出すGET
リクエストのパラメーターはURLSearchParams
を使う。普通に文字列として追加してもよい。- 上の方ので発行したイベントを、
@changepage
でキャッチする。 - propsで渡す
pager
にJSONの中身をぶち込むときに、普通にインデックスを使うとVue.jsが認識できない。this.$set
を使う必要がある (ここ参照)
<pager-block :pager="pager" @changepage="changepage"></pager-block>
var app = new Vue({ el: '#app', data: function() { return { itemList: [], pager: {}, }; }, methods: { loadPage: function(page) { var urlParams = new URLSearchParams(); urlParams.set("page", page); fetch("/api/items?" + urlParams.toString()) .then(response => {return response.json();}) .then(data => { console.log(data); for (var k in data) { if (k === 'content') { continue; } this.$set(this.pager, k, data[k]); } for (var i = 0; i < data.content.length; i++) { this.itemList.push(data.content[i]); } }); }, changepage: function(page) { this.itemList.splice(0); this.loadPage(page); } }, mounted: function() { this.loadPage(0); }, });
MongoDBで集計した上にガッチャンコするやつ
やりたいこと
MongoDBにざっくり以下のようなデータがある。
{ groupId: "aaa", lang: "en", data: "hoge" }, { groupId: "aaa", lang: "ja", data: "ほげほげ" }, { groupId: "bbb", lang: "en", data: "fuga" }, { groupId: "bbb", lang: "fr", data: "titi" }, ...
こいつをもとに、以下のように集計したデータを作りたい。
{ groupId: "aaa", langs: ["en", "ja"] }, { groupId: "bbb", langs: ["en", "fr"] }, ...
やりかた
以下の通り。$group
と$push
を使えば良い。
> db.hoge.aggregate([ ... { $group: { _id: "$groupId", langs: { $push: "$lang" } } }, ... { $sort: { groupId: 1 } } ... ]) { groupId: "aaa", langs: ["en", "ja"] }, { groupId: "bbb", langs: ["en", "fr"] },
参考資料
- 以下の回答そのまんま。
- Aggregation ー MongoDB Manual
$group
$push
その他
例のDynamoDBガッツリ使いたい案件、ある程度柔軟に検索するのであればMongoDBも視野に入る???
DynamoDBでQuery
一つのパーティションの中に26万件データが入っている状態でQueryをいっぱい試してみたけど、1回4KBくらいの読み込みだと、0.01秒くらい。
大したことないな。
>>> sparse.item_count 263132 >>> times = [] >>> for i in range(100): ... before = time.time() ... res = sparse.query( ... TableName='sparse-data', ... KeyConditionExpression='sid=:sid AND unixtime BETWEEN :start_dt AND :end_dt', ... ExpressionAttributeValues={ ... ':sid': 'ABC-1234567', ... ':start_dt': decimal.Decimal(start_dt.timestamp()), ... ':end_dt': decimal.Decimal(end_dt.timestamp()) ... }, ... ReturnConsumedCapacity='TOTAL' ... ) ... times.append(time.time() - before) ... print('consumed capacity: {0:.2f}'.format(res['ConsumedCapacity']['CapacityUnits'])) ... time.sleep(0.5) >>> sum(times) / len(times) 0.019462196826934813
AWS ことはじめ その5 (DynamoDB - Part3)
前回に続いて、今度はデータの読み出し。
やっぱりIDとタイムスタンプ以外でも読み出したいとか、貯めておくのは一旦1ヶ月でいいとか、色々要望がぶれ始めているのでDynamoDBを本当に使うのか雲行きが怪しくなってきている。IDとタイムスタンプ以外で、という件は、Secondary Indexである程度対応できる気はしている。逆に貯めるのを1ヶ月だけでよい、というのは、そもそもKVSを使うほどの容量なのか?という話になってくる。悩ましいところ。
データ格納イメージ
復習。パーティションキーをもとに分散してデータを格納して、同一パーティション内はソートキーで順番にデータを並べる。
1. Primary Keyでアクセス
DynamoDB での項目の操作 - Amazon DynamoDB
最も高速。RDB的に言うとUNIQUE INDEXでアクセスしているようなもの。
2. Queryでのアクセス:
パーティションキーで範囲を絞込み、その中で検索を行う。キャパシティユニットは、結果的に返ってくる項目のサイズによる。つまり、たくさんの属性を持つ項目を読み取ると、1属性だけを返したとしても全属性分のキャパシティユニットが消費される。
>>> sparse.attribute_definitions [{'AttributeName': 'sid', 'AttributeType': 'S'}, {'AttributeName': 'unixtime', 'AttributeType': 'N'}] >>> sparse.key_schema [{'AttributeName': 'sid', 'KeyType': 'HASH'}, {'AttributeName': 'unixtime', 'KeyType': 'RANGE'}] >>> res = sparse.query( ... TableName='sparse-data', ... Limit=10, # 取得する件数を制限(任意) ... ReturnConsumedCapacity='TOTAL', # 消費したキャパシティユニットの総量を返す ... KeyConditionExpression='sid = :val', # 条件式。値は":sid"のようなPlaceholderにする ... ExpressionAttributeValues={ # ↑で指定した Placeholderに対する値を記載する ... ':val': 'ABC-1234567', ... }, ... ConsistentRead=False, # (結果整合性ではなく)強い整合性を持った読み込みを行うか ... ScanIndexForward=False # ソートキーの最初から読み込む(True)か、最後から読み込む(False)か ... ) >>> res['Count'], res['ScannedCount'] # 'Count': 結果の件数、 'ScannedCount': 実際に読み込んだ件数。今回は特にフィルタリングしていないので一致する。 (10, 10) >>> res['ConsumedCapacity'] # 消費したキャパシティユニット。1回の結果整合性のみの読み取り、かつ、1KB以内なので、消費されたRCUは0.5 {'TableName': 'sparse-data', 'CapacityUnits': 0.5} >>> res['LastEvaluatedKey'] # クエリーが読み取った最後の項目。複数のオペレーションに分けるときは、この値をExclusiveStartKeyに含める。 {'sid': 'ABC-1234567', 'unixtime': Decimal('1684587390')} >>> res2 = sparse.query( ... TableName='sparse-data', ... ReturnConsumedCapacity='TOTAL', ... KeyConditionExpression='sid = :sid AND unixtime BETWEEN :start_dt AND :end_dt', ... # ソートキーの条件を指定。条件は一つだけなので、〜以上〜以下のようなケースはBETWEENを使う ... ExpressionAttributeValues={ ... ':sid': 'ABC-1234567', ... ':start_dt': decimal.Decimal(start_dt.timestamp()), # Decimal! ... ':end_dt' : decimal.Decimal(end_dt.timestamp()) ... } ... )
ちなみに、boto3
の(ドキュメント)http://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.Client.queryのExpressionAttributeValuesというセクションに条件で指定する値の書き方が書いてある。が、これ多分間違ってる。例が
{ ":avail":{"S":"Available"}, ":back":{"S":"Backordered"}, ":disc":{"S":"Discontinued"} }
となっているが、正しくは
{":avail": "Available", ":back": "Backordered", ":disc": "Discontinued" }
のように、値部分はdict
ではなく単なる文字列とかを指定する。
3. Scanでのアクセス
テーブル全部を取得する。普通はこれをやる意味はない。全件取得しても大したことない小さなテーブルだとか、そもそもどこかにエクスポートするから全件取得したいとか、そういう特殊なケース向け。