20200802のGoに関する記事は7件です。

Golang+Ginでswaggerを自動生成する(gin-swagger)

はじめに

api開発の時に必須と言っても過言ではないswaggerですが、なんと言っても保守し続けるのがとても面倒くさい、、、
できればコードから勝手にswaggerを生成してくれるといいのですが、、、そんな要望に対しての解決策の1つとなるのがこの

gin-swagger(https://github.com/swaggo/gin-swagger)

です。これはgoのコードに指定のコメントを追加していくことで自動的にswaggerのコードを作成していくためのツールです。

今回のゴール

今回はこのような簡単なCRUDのswaggerをgin-swaggerを用いて作成していきたいと思います!
スクリーンショット 2020-08-02 22.13.02.png

ディレクトリツリーはこんな感じです!

rootディレクトリはgin-swagger-testとしています

gin-swagger-test
.
├── docs # ←自動生成されるディレクトリ
│   ├── docs.go 
│   ├── swagger.json
│   └── swagger.yaml
├── go.mod
├── go.sum
├── handlers
│   └── todo.go
├── main.go
├── models
│   └── todo.go
└── responses
    ├── errors.go
    └── success.go

セットアップ

まずはセットアップです

$ go get -u github.com/swaggo/swag/cmd/swag
$ go get -u github.com/swaggo/gin-swagger
$ go get -u github.com/swaggo/files

# 下記コマンド実行後root配下に`docs`ディレクトリが作成される
$ swag init ./main.go

# もし`swag not found`的な警告が出た場合は下記コマンドを実行し改めて `swag init`を行う
$ export PATH=$(go env GOPATH)/bin:$PATH

参考文献:https://github.com/swaggo/swag/issues/197

main.go
package main

import (
    "github.com/gin-gonic/gin"

    // docsのディレクトリを指定
    _ "gin-swagger-test/docs" // ←追記

    ginSwagger "github.com/swaggo/gin-swagger" // ←追記
    "github.com/swaggo/gin-swagger/swaggerFiles" // ←追記
)

func main() {
    r := gin.Default()

    // 下記を追記することで`http://localhost:8080/swagger/index.html`を叩くことでswagger uiを開くことができる
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

    r.Run()
}

ベースのアノテーション

まずはこの辺りの記述方法です
スクリーンショット 2020-08-02 21.07.33.png

もっとたくさんのアノテーションがありますが、最低限必要なのは

  • title
  • version
  • license.name

の3つのみです
より詳細なアノテーションをつけたい場合はこちらを参照してください

main.go
// @title gin-swagger todos 
// @version 1.0
// @license.name kosuke
// @description このswaggerはgin-swaggerの見本apiです
func main() {
    r := setupRouter()
    r.Run()
}

handlerアノテーション

Getリクエスト

スクリーンショット 2020-08-02 21.12.11.png

handlers/todo.go
// GetTodos ...                                                        
// @Summary Todo一覧を配列で返す                                          -> ①どのようなエンドポイントなのかを端的に示すコメント
// @Tags Todo                                                          -> ②tagの指定、tagベースでエンドポイントをグループ化する時に役立つ
// @Produce  json                                                      -> ③どのような形のデータを返すかを指定
// @Success 200 {object} responses.SuccessResponse{data=[]models.Todo} -> ④successレスポンス
// @Failure 400 {object} responses.ErrorResponse                       -> ⑤errorレスポンス
// @Router /todos [get]                                                -> ⑥ルーティング
func (t *Todo) GetTodos(c *gin.Context) {
    todos, err := models.PostTodo() // ←何かしらのTodo配列を返す処理
    if err != nil {
        // エラーハンドリング
    }
    c.JSON(200, todos)
}

基本的にswaggerを書いたことのある人であれば「なるほどなぁ〜」となる内容だとは思うのですが、1点よくわからないところがあるとするなら

// @Success 200 {object} responses.SuccessResponse{data=[]models.Todo} -> ④successレスポンス
// @Failure 400 {object} responses.ErrorResponse                       -> ⑤errorレスポンス

上記のあたりでしょうか?
successレスポンス、errorレスポンスはそれぞれ
@Success ステータスコード 返り値の型 値
と言うような引数をとります。

また、todo配列のjsonを返したい場合はレスポンス用のstructを用意しそこに対して
responses.SuccessResponse{data=[]models.Todo}
と言うような形で配列のtodoを代入することで実現します

responses/success.go
// SuccessResponse ...
type SuccessResponse struct {
    Data interface{} `json: "data"`
}
models/todo.go
// Todo ...
type Todo struct {
    ID    int    `json:"id" example:"1"`
    Title string `json:"title" example:"title1"`
    Body  string `json:"body" example:"body1"`
}
responses/errors.go
// ErrorResponse ...
type ErrorResponse struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
}

Postリクエスト

スクリーンショット 2020-08-02 21.57.21.png

handlers/todo.go
// PostTodo ...
// @Summary 新規Todoを作成
// @Tags Todo
// @Accept  json                         -> ①受け取るデータ型を指定
// @Produce  json
// @Param title body string true "title" -> ②受け取るパラメータを指定
// @Param body body string true "body"
// @Success 201 {object} responses.SuccessResponse{data=models.Todo}
// @Failure 400 {object} responses.ErrorResponse
// @Router /todos [post]
func (t *Todo) PostTodo(c *gin.Context) {
    todos, err := models.PostTodo() // ←何かしらの新規Todoを作成する処理
    if err != nil {
        c.JSON(400, &responses.ErrorResponse{Code: 400, Message: "bad request"})
    }
    c.JSON(200, todos)
}

postリクエストでつまずくところは

// @Param title body string true "title" -> ②受け取るパラメータを指定
// @Param body body string true "body"

あたりのパラメータの指定方法だと思いますこれは
スクリーンショット 2020-08-02 22.00.53.png

@Param パラメータ名 パラメータの場所(header, body...) データ型 必須項目か? コメント
と言うような引数を与えることでパラメータを指定しています

Patchリクエスト

スクリーンショット 2020-08-02 22.12.38.png

handlers/todo.go
// PatchTodo ...
// @Summary 既存Todoを更新
// @Tags Todo
// @Accept  json
// @Produce  json
// @Param id path int true "id"
// @Param title body string false "title"
// @Param body body string false "body"
// @Success 200 {object} responses.SuccessResponse{data=models.Todo}
// @Failure 400 {object} responses.ErrorResponse
// @Router /todos/{id} [patch]
func (t *Todo) PatchTodo(c *gin.Context) {
    todos, err := models.PatchTodo() // ←何かしらのTodoを更新する作成する処理
    if err != nil {
        c.JSON(400, &responses.ErrorResponse{Code: 400, Message: "bad request"})
    }
    c.JSON(200, todos)
}

Deleteリクエスト

スクリーンショット 2020-08-02 22.12.25.png

handlers/todo.go
// DeleteTodo ...
// @Summary 既存Todoを削除
// @Tags Todo
// @Accept  json
// @Produce  json
// @Param id path int true "id"
// @Success 201 {object} responses.SuccessResponse{data=models.Todo}
// @Failure 400 {object} responses.ErrorResponse
// @Router /todos/{id} [delete]
func (t *Todo) DeleteTodo(c *gin.Context) {
    todos, err := models.DeleteTodo() // ←何かしらのTodoを削除する処理
    if err != nil {
        c.JSON(400, &responses.ErrorResponse{Code: 400, Message: "bad request"})
    }
    c.JSON(200, todos)
}

以上です。
アノテーションを修正後は改めて

$ swag init ./main.go

とすることで既存のswaggerを上書きしてくれます

まとめ

swaggerを自動生成するためにgin-swagger(swag)の書き方を覚えるのは多少めんどくさい部分があるのは否めないですが、自動生成をしてあげることで

  • 書き方の統一感を出せる
  • ドキュメントと実装の乖離する可能性を多少防げる

と言うメリットは確実にあるので
試しに採用する価値はあると思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Istioはいかにしてサービス通信のセキュリティを担保しているのか?

サービス間通信で担保したいものと従来のセキュリティモデル

サービス間通通信として担保したいものとして、
通信するアクセス元を制御し、盗聴やなりすまし、改ざんといった攻撃から守ることが挙げられる。
つまり、

  • man in the middle攻撃への防御
  • アクセス制御

がセキュリティ要件として挙げられる。
従来のセキュリティモデルとしては、
プライベートネットワークを構築し、IPベースでアクセス元を制限することでこれらを担保してきた。
(AWSのPrivate VPCとSGがわかりやすい)

Istioではどういうアプローチをとったのか?

昨今のコンテナベースで構築されるシステムでは、IPアドレスは高頻度で動的に変更されるためこのアプローチだと破綻する。
そこでIsitoは各ワークロードに対してIDを定義し、IDベースでアクセス制御を行うことととした。
具体的にはどういうアプローチなのか?

コアコンセプト

ワークロードに割り振られたIDをベースとしてTLS通信することで認証と暗号化通信をし、
認証されたIDに対する認可制御を行うことでアクセス制御を実現している。
そのために下記の仕組みを実装している。

  1. ワークロードに一意のIDを割り振る
  2. IDを認証するための証明書の発行、配布、ローテーション

これらは、SPIFFE(Secure Production Identity Framework For Everyone)と呼ばれる標準仕様で定義されており、IstioはCitadelコンポーネントがこれを踏襲した実装にあたる。
具体的なSPIFEEの仕様、フローはSPIFFEとその実装であるSPIREについてでわかりやすく言及されている。

IstioによるSPIFEE実装

ポイント

つまり、ワークロードのためのID、証明書を発行し、登録しておくための認証局としての役割を担ったisdiod(Citadel)が存在し、各ノードに存在するistio-agentが、このCitadelとistio proxyの間のやり取りを仲介し、証明書配布などを行っている。
(istio proxyのSDSはこのagentが担当している)
これによって、Podが大量に存在するような大規模クラスタでも、Pod自体はistio agentによって分割統治されているので、istiodに対しての負荷をかなり抑えることができる。

また、通常想定されるTLSと違い、この証明書の期限は非常に短く設定されていて、頻繁にローテーションされている。
これによって攻撃者は攻撃を継続していくためにはこの証明書をローテーションされるたびに逐一盗み出さなくてはならず、攻撃の難易度を上げている。

まとめ

このようにIstioはワークロードレベルで認証し、それぞれにアクセス制御を設けている。
これによって動いているプラットフォームに左右されず、どのような環境においても安全な通信を実現することができる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Istioはいかにしてサービス間通信のセキュリティを担保しているのか?

この記事について

この記事では、Istioがどのような考え方でサービス間通信のセキュリティを担保し、
どのように担保しているかを概観レベルで整理する。

サービス間通信で担保したいものと従来のセキュリティモデル

サービス間通信として担保したいものとして、
通信するアクセス元を制御し、盗聴やなりすまし、改ざんといった攻撃から守ることが挙げられる。
つまり、

  • man in the middle攻撃への防御
  • アクセス制御

がセキュリティ要件として挙げられる。
従来のセキュリティモデルとしては、
プライベートネットワークを構築し、IPベースでアクセス元を制限することでこれらを担保してきた。
(AWSのPrivate VPCとSGがわかりやすい)

Istioではどういうアプローチをとったのか?

昨今のコンテナベースで構築されるシステムでは、IPアドレスは高頻度で動的に変更されるためこのアプローチだと破綻する。
そこでIsitoは各ワークロードに対してIDを定義し、IDベースでアクセス制御を行うことととした。
具体的にはどういうアプローチなのか?

コアコンセプト

ワークロードに割り振られたIDをベースとしてTLS通信することで認証と暗号化通信をし、
認証されたIDに対する認可制御を行うことでアクセス制御を実現している。
そのために下記の仕組みを実装している。

  1. ワークロードに一意のIDを割り振る
  2. IDを認証するための証明書の発行、配布、ローテーション

これらは、SPIFFE(Secure Production Identity Framework For Everyone)と呼ばれる標準仕様で定義されており、IstioはCitadelコンポーネントがこれを踏襲した実装にあたる。
具体的なSPIFEEの仕様、フローはSPIFFEとその実装であるSPIREについてでわかりやすく言及されている。

IstioによるSPIFEE実装

ポイント

つまり、ワークロードのためのID、証明書を発行し、登録しておくための認証局としての役割を担ったisdiod(Citadel)が存在し、各ノードに存在するistio-agentが、このCitadelとistio proxyの間のやり取りを仲介し、証明書配布などを行っている。
(istio proxyのSDSはこのagentが担当している)
これによって、Podが大量に存在するような大規模クラスタでも、Pod自体はistio agentによって分割統治されているので、istiodに対しての負荷をかなり抑えることができる。

また、通常想定されるTLSと違い、この証明書の期限は非常に短く設定されていて、頻繁にローテーションされている。
これによって攻撃者は攻撃を継続していくためにはこの証明書をローテーションされるたびに逐一盗み出さなくてはならず、攻撃の難易度を上げている。

まとめ

このようにIstioはワークロードレベルで認証し、それぞれにアクセス制御を設けている。
これによって動いているプラットフォームに左右されず、どのような環境においても安全な通信を実現することができる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

golang の datastore の unittest でハマった話

概要

datastore.NewClient は実在の projectId を要求する

  • context は aetest.NewContext を使う
  • datastore client はテストでも使い方は同じ
  • projectId が非実在のものだとエラーになる
  • 実際の datastore には書き込まれていないのは確認済み

--> テストでも実在の ProjectId を指定する必要がある

少し調べた範囲ではこれに関する情報が見つけられなかったため、あくまで推論。情報があれば教えて下さい。

コード

func NewClient(ctx context.Context, projectId string) (*datastore.Client, error){
    return datastore.NewClient(ctx, ProjectId)
}
func TestDatastore(t *testing.T) {
  ctx, done, err := aetest.NewContext()
  if err != nil {
    t.Fatal(err)
  }
  defer done()

  // NG
  if c, err = NewClient(ctx, "dummy-project"); err != nil {
    t.Fatal(err)
  }

  // OK
  if c, err = NewClient(ctx, "real-project-12345"); err != nil {
    t.Fatal(err)
  }

  // ...
}

追記

luci/gae というのを使うと高速にできるようです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

カリー化チートシート

拙作『不動点コンビネータを用いた無名再帰関数の実行まとめ』の補足説明として書き始めたところ,カリー化関数を記述・利用するための独立したチートシートとした方が少なくとも約1名(自分自身)には役立ちそうだったので,新しく記事にした.なお,カリー化してくれる関数の定義ではないことに注意.複数言語にわたっている都合上,各言語に精通している方々のツッコミ歓迎.

記法のみの一覧は次の通り.なお,fは関数,aは引数を指す.

言語 各引数の戻り値 各引数の指定 備考
Haskell \->による無名関数 (・・・(f a) a)・・・a 自動的にカリー化
Scheme lambdaによる無名関数 (・・・((f a) a)・・・a)
Python 関数内部で定義した関数 f(a)(a)・・・(a) 無名関数も使用可
Ruby ->を用いた無名関数 f.(a).(a)・・・.(a)またはf[a][a]・・・[a] カリー化メソッドあり
JavaScript =>functionを用いた無名関数 f(a)(a)・・・(a)
Scala =>を用いた無名関数 f(a)(a)・・・(a) カリー化メソッドあり,強い型付け
Perl subを用いた無名サブルーチン f(a)->(a)・・・->(a) $f->(a)・・・の場合あり
Go言語 funcを用いた無名関数 f(a)(a)・・・(a) 強い型付け
PHP functionuseを用いた無名関数 f(a)(a)・・・(a) 7.4よりfn=>を用いた無名関数が利用可

Haskell(GHC)

Haskellでは,複数引数で定義しても自動的にカリー化される.カリー化関数の引数指定は(・・・(関数 引数) 引数)・・・引数である.

Prelude> func x y z = if x > 0 then y else z
Prelude> func (-100) 0 (-1)
-1
Prelude> ((func (-100)) 0) (-1)
-1

\および->を用いた無名関数を戻り値にして実現する方法は次の通り.ただし,この場合でも引数の複数指定が可能.

Prelude> func = \x -> \y -> \z -> if x > 0 then y else z
Prelude> ((func (-100)) 0) (-1)
-1
Prelude> func (-100) 0 (-1)
-1
Prelude> func = \x y z -> if x > 0 then y else z
Prelude> ((func (-100)) 0) (-1)
-1
Prelude> func (-100) 0 (-1)
-1

Scheme(Gauche)

lambdaを用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は(・・・((関数 引数) 引数)・・・引数)である.

gosh> (define (func x y z) (if (> x 0) y z))
func
gosh> (func -100 0 -1)
-1
gosh> (define func (lambda (x) (lambda (y) (lambda (z) (if (> x 0) y z)))))
func
gosh> (((func -100) 0) -1)
-1

Python(Python3,Python2)

lambdaを用いた無名関数を用いることもできるが,無名関数を直接変数に代入するのがPEP8非推奨ということもあり,関数内部で定義した関数を戻り値にする方法が一般的である.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)である.

>>> def func(x, y, z): return y if x > 0 else z
... 
>>> func(-100, 0, -1)
-1
>>> def func(x):
...     def func(y):
...         def func(z): return y if x > 0 else z
...         return func
...     return func
... 
>>> func(-100)(0)(-1)
-1
>>> func = lambda x: lambda y: lambda z: y if x > 0 else z    # PEP8非推奨
>>> func(-100)(0)(-1)
-1

Ruby(CRuby,JRuby)

Rubyでは,カリー化するメソッドcurryが用意されている.ただし,複数引数の無名関数を->によって一度定義してからcurryを適用する.カリー化関数の引数指定は関数.(引数).(引数)・・・.(引数)または関数[引数][引数]・・・[引数]である.

def func1(x,y,z) x > 0 ? y : z end
p func1(-100,0,-1)              # => -1

func2 = -> x,y,z { x > 0 ? y : z }
p func2.curry.(-100).(0).(-1)    # => -1
p func2.curry[-100][0][-1]       # => -1

->のみを用いた無名関数によって実現する方法は次の通り.

func3 = -> x { ->  y { -> z { x > 0 ? y : z } } }
p func3.(-100).(0).(-1)          # => -1
p func3[-100][0][-1]             # => -1

JavaScript(Node.js)

=>functionを用いた無名関数を戻り値にする方法で実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)である.

function func1(x,y,z) { return x > 0 ? y : z }
console.log(func1(-100,0,-1))      // => -1

func2 = x => y => z => x > 0 ? y : z
console.log(func2(-100)(0)(-1))    // => -1

function func3(x) {
  return function (y) {
    return function (z) {
      return x > 0 ? y : z
    }
  }
}
console.log(func3(-100)(0)(-1))    // => -1

Scala(Scala 2.11 + Java VM 12)

Scalaでは,カリー化するメソッドcurriedが用意されている.ただし,複数引数の無名関数を=>によって一度定義してからcurriedを適用する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)である.

scala> def func(x: Int, y: Int, z: Int): Int = if (x > 0) y else z
func: (x: Int, y: Int, z: Int)Int

scala> func(-100,0,-1)
res0: Int = -1

scala> val func = (x: Int, y: Int, z: Int) => if (x > 0) y else z
func: (Int, Int, Int) => Int = <function3>

scala> val func_curried = func.curried
func_curried: Int => (Int => (Int => Int)) = <function1>

scala> func_curried(-100)(0)(-1)
res1: Int = -1

=>のみを用いた無名関数によって実現する方法は次の通り.強い型付け言語であるため,関数全体の型の推移を明示する必要がある.

scala> val func: Int => (Int => (Int => Int)) = (x: Int) => (y: Int) => (z: Int) => if (x > 0) y else z
func: Int => (Int => (Int => Int)) = <function1>

scala> func(-100)(0)(-1)
res2: Int = -1

Perl(perl 5)

subを用いた無名関数(サブルーチン)を戻り値にして実現する.カリー化関数の引数指定は関数(引数)->(引数)・・・->(引数)である.なお,関数本体の名前も無名関数とした場合は$関数->(引数)->(引数)・・・->(引数)である.

sub func { my ($x,$y,$z) = @_; $x > 0 ? $y : $z; };
print func(-100,0,-1), "\n";    # => -1

sub func_curried { my $x = shift; return sub { my $y = shift; return sub { my $z = shift; return $x > 0 ? $y : $z; }; }; };
print func_curried(-100)->(0)->(-1), "\n";    # => -1

my $func_curried2 = sub { my $x = shift; return sub { my $y = shift; return sub { my $z = shift; return $x > 0 ? $y : $z; }; }; };
print $func_curried2->(-100)->(0)->(-1), "\n";    # => -1

Go言語(gc)

funcを用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)である.なお,強い型付け言語であるため,扱う引数が増えるほど,各引数の関数の戻り値に対する型付け記述が増えていく.

package main
import "fmt"

func func1 (x, y, z int) int { if x > 0 { return y } else { return z } }
func func2 (x int) func(int) func(int) int {
    return func(y int) func(int) int {
        return func(z int) int {
            if x > 0 { return y } else { return z }
        }
    }
}

func main() {
    fmt.Println(func1(-100,0,-1))      // => -1
    fmt.Println(func2(-100)(0)(-1))    // => -1
}

PHP(PHP 7.3,PHP 7.4)

PHP7.3までは,functionuseを用いた無名関数を戻り値にする方法で実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)である.

<?php

function func1($x,$y,$z) {
    return ($x > 0) ? $y : $z;
}
echo func1(-100,0,-1) . PHP_EOL;
// => -1

function func2($x) {
    return function($y) use ($x) {
        return function($z) use ($x,$y) {
            return ($x > 0) ? $y : $z;
        };
    };
}
echo func2(-100)(0)(-1) . PHP_EOL;
// => -1

PHP 7.4からは,fn=>を用いた無名関数が利用可能.

function func2($x) { return fn($y) => fn($z) =>  ($x > 0) ? $y : $z; }
echo func2(-100)(0)(-1) . PHP_EOL;
// => -1

備考

カリー化の概要

カリー化とは,高階関数の機能を利用して,複数の引数を指定する関数を,ひとつの引数のみを指定する関数の繰り返しに変換することである.処理記述の共有が可能な『部分適用』の手段として有名であるが,あくまで利用例のひとつである.カリー化自体の特徴は,ラムダ計算などの数学的な理論を適用しやすいことに加え,引数を再帰的に受け取る汎用的な関数を定義したり,引数ごとに値の適用を調節したり,データ構造を要素ごとに受け取ったりすることで,より簡潔で柔軟なプログラミングが可能となることである.

なお,下記のPythonの例のように,今回の記述方法を用いることで,複数引数をもつ既存の関数のカリー化も容易に行える.ただし,複数引数をもつ既存の関数をカリー化関数に変換してくれる関数やマクロは作成できない.理由は,既存関数の引数の数が不定であり,任意の数の無名関数や内部関数を(クロージャ機能を含めて)生成できないためである.RubyやScalaのカリー化メソッドは個別に型定義された複数引数をもつ無名関数から変換しており,Haskellは言語仕様としてカリー化関数のみを扱っている(関数定義を行った時点で,型をもつ引数ごとのカリー化関数となる).

Pythonでのカリー化関数利用例

>>> def func(y):
...     def func(z):
...         def func(w):
...             def func(x): return y if x > 0 else z if x < 0 else w
...             return func
...         return func
...     return func
... 
>>> func1 = func('positive')('negative')
>>> func2 = func1('zero')
>>> func2(1)
'positive'
>>> func2(-1)
'negative'
>>> func2(0)
'zero'
>>> func2 = func1('ゼロ')
>>> func2(0)
'ゼロ'
>>> T = (3, -2, 0, 1, -7)
>>> dict(zip(T, map(func2, T)))
{3: 'positive', -2: 'negative', 0: 'ゼロ', 1: 'positive', -7: 'negative'}
>>> def recur(f, t): return f if not t else recur(f(t[0]), t[1:])
... 
>>> dict(zip(T, map(recur(func, ('正', '負', 'ゼロ')), T)))
{3: '正', -2: '負', 0: 'ゼロ', 1: '正', -7: '負'}
>>> def is_t(t):
...     def r(v): return isinstance(v, t)
...     return r
... 
>>> T = 10, "hoge", 20.4, False, "hage"
>>> tuple(map(is_t(str), T))
(False, True, False, False, True)
>>> tuple(map(is_t(int), T))
(True, False, False, True, False)

(同様の内容をSchemeで記述したものはこちら

変更履歴

  • 2020-08-03:カリー化の概要説明部分を修正(コメントより)
  • 2020-08-03:Rubyのカリー化関数を追加(コメントより)
  • 2020-08-03:PHP 7.4を追加(コメントより)
  • 2020-08-02:PHPを追加
  • 2020-08-02:記法のみの一覧を追加
  • 2020-08-02:Go言語を追加
  • 2020-08-02:初版公開(Haskell,Scheme,Python,Ruby,JavaScript,Scala,Perl,Python利用例)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goのldapモジュールで認証をLDAP認証をかける

認証したいユーザー

  • cn=yuta

ldapモジュールをインストール

go get github.com/go-ldap/ldap

サンプルコード

package main

import (
    "fmt"
    "github.com/go-ldap/ldap/v3"
    "log"
)

var (
    ldapServer = "ldap://localhost:389"
    baseDN     = "dc=vamdemic,dc=black"
    username = "yuta"
    password = "password"
    bindusername = "cn=admin,dc=vamdemic,dc=black"
    bindpassword = "password"

)

// This example shows how a typical application can verify a login attempt
func Example_userAuthentication() {
    l, err := ldap.DialURL(ldapServer)
    if err != nil {
        log.Fatal(err)
    }
    defer l.Close()

    // First bind with a read only user
    err = l.Bind(bindusername, bindpassword)
    if err != nil {
        log.Fatal(err)
    }

    // Search for the given username
    searchRequest := ldap.NewSearchRequest(
        baseDN,
        ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
        fmt.Sprintf("(&(objectClass=organizationalPerson)(cn=%s))", username),
        []string{"dn"},
        nil,
    )

    sr, err := l.Search(searchRequest)
    if err != nil {
        log.Fatal(err)
    }

    if len(sr.Entries) != 1 {
        log.Fatal("User does not exist or too many entries returned")
    }

    userdn := sr.Entries[0].DN

    // Bind as the user to verify their password
    err = l.Bind(userdn, password)
    if err != nil {
        log.Fatal(err)
    }

    // Rebind as the read only user for any further queries
    err = l.Bind(bindusername, bindpassword)
    if err != nil {
        log.Fatal(err)
    }
}

func main() {
    Example_userAuthentication()
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

golangでOpenLDAPサーバへ接続する

サーバを立てる

  • DockerでOpenLdapを389で起動します

https://qiita.com/yuta_vamdemic/items/53cd67eea5af71ffd08a

認証する

Ldapモジュールをインストール

go get github.com/go-ldap/ldap

コード

package main

import (
    "crypto/tls"
    "errors"
    "fmt"
    "github.com/go-ldap/ldap"
)

func ExampleconnSearch() (bool, error) {

    ldapServer   := "ldap://localhost:389"
    err := errors.New("connection error")

    // ldapServerへの接続確認
    l, err := ldap.DialURL(ldapServer, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
    // 接続失敗時のエラーハンドリング
    if err != nil {
        fmt.Printf("%s\n", "ldap connection error")
        return false, err
    }

    fmt.Printf("%s\n", "ldap connection success")
    defer l.Close()
    return true, nil
}

func main() {
    ExampleconnSearch()
}

実行結果

$ go run main.go
ldap connection success
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む