- 投稿日:2020-08-02T22:19:32+09:00
Golang+Ginでswaggerを自動生成する(gin-swagger)
はじめに
api開発の時に必須と言っても過言ではない
swagger
ですが、なんと言っても保守し続けるのがとても面倒くさい、、、
できればコードから勝手にswaggerを生成してくれるといいのですが、、、そんな要望に対しての解決策の1つとなるのがこのgin-swagger(https://github.com/swaggo/gin-swagger)
です。これはgoのコードに指定のコメントを追加していくことで自動的にswaggerのコードを作成していくためのツールです。
今回のゴール
今回はこのような簡単なCRUDのswaggerを
gin-swagger
を用いて作成していきたいと思います!
ディレクトリツリーはこんな感じです!
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.gopackage 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() }ベースのアノテーション
もっとたくさんのアノテーションがありますが、最低限必要なのは
- 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リクエスト
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リクエスト
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"
@Param パラメータ名 パラメータの場所(header, body...) データ型 必須項目か? コメント
と言うような引数を与えることでパラメータを指定していますPatchリクエスト
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リクエスト
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)の書き方を覚えるのは多少めんどくさい部分があるのは否めないですが、自動生成をしてあげることで
- 書き方の統一感を出せる
- ドキュメントと実装の乖離する可能性を多少防げる
と言うメリットは確実にあるので
試しに採用する価値はあると思います。
- 投稿日:2020-08-02T21:12:51+09:00
Istioはいかにしてサービス通信のセキュリティを担保しているのか?
サービス間通信で担保したいものと従来のセキュリティモデル
サービス間通通信として担保したいものとして、
通信するアクセス元を制御し、盗聴やなりすまし、改ざんといった攻撃から守ることが挙げられる。
つまり、
- man in the middle攻撃への防御
- アクセス制御
がセキュリティ要件として挙げられる。
従来のセキュリティモデルとしては、
プライベートネットワークを構築し、IPベースでアクセス元を制限することでこれらを担保してきた。
(AWSのPrivate VPCとSGがわかりやすい)Istioではどういうアプローチをとったのか?
昨今のコンテナベースで構築されるシステムでは、IPアドレスは高頻度で動的に変更されるためこのアプローチだと破綻する。
そこでIsitoは各ワークロードに対してIDを定義し、IDベースでアクセス制御を行うことととした。
具体的にはどういうアプローチなのか?コアコンセプト
ワークロードに割り振られたIDをベースとしてTLS通信することで認証と暗号化通信をし、
認証されたIDに対する認可制御を行うことでアクセス制御を実現している。
そのために下記の仕組みを実装している。
- ワークロードに一意のIDを割り振る
- IDを認証するための証明書の発行、配布、ローテーション
これらは、SPIFFE(Secure Production Identity Framework For Everyone)と呼ばれる標準仕様で定義されており、IstioはCitadelコンポーネントがこれを踏襲した実装にあたる。
具体的なSPIFEEの仕様、フローはSPIFFEとその実装であるSPIREについてでわかりやすく言及されている。ポイント
つまり、ワークロードのためのID、証明書を発行し、登録しておくための認証局としての役割を担ったisdiod(Citadel)が存在し、各ノードに存在するistio-agentが、このCitadelとistio proxyの間のやり取りを仲介し、証明書配布などを行っている。
(istio proxyのSDSはこのagentが担当している)
これによって、Podが大量に存在するような大規模クラスタでも、Pod自体はistio agentによって分割統治されているので、istiodに対しての負荷をかなり抑えることができる。また、通常想定されるTLSと違い、この証明書の期限は非常に短く設定されていて、頻繁にローテーションされている。
これによって攻撃者は攻撃を継続していくためにはこの証明書をローテーションされるたびに逐一盗み出さなくてはならず、攻撃の難易度を上げている。まとめ
このようにIstioはワークロードレベルで認証し、それぞれにアクセス制御を設けている。
これによって動いているプラットフォームに左右されず、どのような環境においても安全な通信を実現することができる。
- 投稿日:2020-08-02T21:12:51+09:00
Istioはいかにしてサービス間通信のセキュリティを担保しているのか?
この記事について
この記事では、Istioがどのような考え方でサービス間通信のセキュリティを担保し、
どのように担保しているかを概観レベルで整理する。サービス間通信で担保したいものと従来のセキュリティモデル
サービス間通信として担保したいものとして、
通信するアクセス元を制御し、盗聴やなりすまし、改ざんといった攻撃から守ることが挙げられる。
つまり、
- man in the middle攻撃への防御
- アクセス制御
がセキュリティ要件として挙げられる。
従来のセキュリティモデルとしては、
プライベートネットワークを構築し、IPベースでアクセス元を制限することでこれらを担保してきた。
(AWSのPrivate VPCとSGがわかりやすい)Istioではどういうアプローチをとったのか?
昨今のコンテナベースで構築されるシステムでは、IPアドレスは高頻度で動的に変更されるためこのアプローチだと破綻する。
そこでIsitoは各ワークロードに対してIDを定義し、IDベースでアクセス制御を行うことととした。
具体的にはどういうアプローチなのか?コアコンセプト
ワークロードに割り振られたIDをベースとしてTLS通信することで認証と暗号化通信をし、
認証されたIDに対する認可制御を行うことでアクセス制御を実現している。
そのために下記の仕組みを実装している。
- ワークロードに一意のIDを割り振る
- IDを認証するための証明書の発行、配布、ローテーション
これらは、SPIFFE(Secure Production Identity Framework For Everyone)と呼ばれる標準仕様で定義されており、IstioはCitadelコンポーネントがこれを踏襲した実装にあたる。
具体的なSPIFEEの仕様、フローはSPIFFEとその実装であるSPIREについてでわかりやすく言及されている。ポイント
つまり、ワークロードのためのID、証明書を発行し、登録しておくための認証局としての役割を担ったisdiod(Citadel)が存在し、各ノードに存在するistio-agentが、このCitadelとistio proxyの間のやり取りを仲介し、証明書配布などを行っている。
(istio proxyのSDSはこのagentが担当している)
これによって、Podが大量に存在するような大規模クラスタでも、Pod自体はistio agentによって分割統治されているので、istiodに対しての負荷をかなり抑えることができる。また、通常想定されるTLSと違い、この証明書の期限は非常に短く設定されていて、頻繁にローテーションされている。
これによって攻撃者は攻撃を継続していくためにはこの証明書をローテーションされるたびに逐一盗み出さなくてはならず、攻撃の難易度を上げている。まとめ
このようにIstioはワークロードレベルで認証し、それぞれにアクセス制御を設けている。
これによって動いているプラットフォームに左右されず、どのような環境においても安全な通信を実現することができる。
- 投稿日:2020-08-02T13:35:06+09:00
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 というのを使うと高速にできるようです
- 投稿日:2020-08-02T07:14:25+09:00
カリー化チートシート
拙作『不動点コンビネータを用いた無名再帰関数の実行まとめ』の補足説明として書き始めたところ,カリー化関数を記述・利用するための独立したチートシートとした方が少なくとも約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 function
とuse
を用いた無名関数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) -1Scheme(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) -1Python(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) -1Ruby(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] # => -1JavaScript(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)) // => -1Scala(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 = -1Perl(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"; # => -1Go言語(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までは,
function
とuse
を用いた無名関数を戻り値にする方法で実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.<?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; // => -1PHP 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利用例)
- 投稿日:2020-08-02T03:32:13+09:00
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() }
- 投稿日:2020-08-02T00:20:34+09:00
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