Slide 1

Slide 1 text

権威DNSサービスへのDDoSと 
 ハイパフォーマンスなベンチマーカ YAPC::Kyoto 2023 at Kyoto Research Park 2023/03/19 さくらインターネット株式会社 クラウド事業本部 SRE室 Masahiro Nagano (kazeburo)

Slide 2

Slide 2 text

Me • ⻑野雅広(ながのまさひろ) • CPAN: KAZEBURO • Twitter/GitHub @kazeburo • 2006年まで京都リサーチパーク4号館で勤務 • さくらインターネット株式会社 クラウド事業本部 
 SRE室 室⻑ • ISUCON1, 2, 9予選出題。ISUCON3, 4優勝

Slide 3

Slide 3 text

さくらインターネット SRE室の取り組み • ミッション • クラウドサービスの信頼性を⾼めることにより、お客様や社会のDXをしっかり⽀える • ビジョン • 社内でのSREの実践を広め、お客様への価値提供を⾏う • さくらのサービスそのものの信頼性向上、それにより価値向上を⽬指す • さくら社員がEnabling SREとして、お客様・社外のサービスの信頼性向上に携わる

Slide 4

Slide 4 text

さくらインターネット SRE室の取り組み • SRE as a Service • 社内における Kubernetes 基盤構築 • ログ/監視基盤の研究開発 • Embedded SRE / Enabling SREとしての取り組み  • クラウドサービスのチーム開発/運⽤体制作り • CI/CDなどDX(Develoer Experience)向上の仕組みの構築 • 既存クラウドサービスの開発運⽤

Slide 5

Slide 5 text

DNS⽔責め攻撃 ランダムサブドメイン

Slide 6

Slide 6 text

「DNS⽔責め攻撃」とは • 攻撃対象に⼤量のランダムなサブドメイン(foo-bar-baz.example.com)を問 い合わせてDNSの機能停⽌、機能低下を狙う攻撃 • 攻撃者はオープンリゾルバ(DNSキャッシュサーバ)に対して、⼤量のラン ダムサブドメインの問い合わせを発⽣させる • DNSキャッシュサーバにはキャッシュがなく、権威DNSサーバに⼤量の問 い合わせが発⽣し、DDoSとなる

Slide 7

Slide 7 text

「DNS⽔責め攻撃」とは • ランダムサブドメイン攻撃 (Pseudo-Random Subdomain Attack) と呼ばれ ることも • 2014年に初めて観測 (https://cybersecurity-jp.com/column/34745) • DNSによる名前解決として正しい動作、通信パケットであり防ぐのが難しい • GoogleやCloud fl areのPublic DNSが利⽤されるため、単純なブロックがで きない

Slide 8

Slide 8 text

実際の攻撃の記録(1分間あたりのクエリ数) 12/15から12/16まで1⽇近く、900万クエリ/分(15万qps)の攻撃が続いた

Slide 9

Slide 9 text

実際の攻撃(tcpdump) 07:25:11.719035 IP 209.216.160.2.50051 > 133.242.64.100.53: 43104 A? meetmodeling.example.com. (50) 07:25:11.719057 IP 205.171.30.238.44916 > 133.242.64.100.53: 64321% [1au] A? _.modeling.example.com. (71) 07:25:11.719069 IP 172.70.109.31.63292 > 133.242.64.100.53: 40380 [1au] A? osaExpe1-pLatINUM.exAmpLe.cOm. (66) 07:25:11.719071 IP 3.139.136.204.44597 > 133.242.64.100.53: 32383% [1au] A? webdirect.foster.example.com. (65) 07:25:11.719113 IP 18.188.77.103.42513 > 133.242.64.100.53: 14853 [1au] A? note-modeling.example.com. (62) 07:25:11.719132 IP 172.70.33.19.27971 > 133.242.64.100.53: 35379 [1au] A? indian-awarded.example.com. (63) 07:25:11.719147 IP 12.121.89.48.43564 > 133.242.64.100.53: 23891 A? matchfiling.example.com. (49) 07:25:11.719156 IP 74.125.181.130.64517 > 133.242.64.100.53: 25285% [1au] A? xmL.mODeLING.eXaMple.CoM. (61) 07:25:11.719166 IP 165.225.41.202.17203 > 133.242.64.100.53: 53044% [1au] A? qatawarded.example.com. (59) 07:25:11.719176 IP 96.114.53.67.20082 > 133.242.64.100.53: 41999 [1au] A? netherlands.filing.example.com. (67) 07:25:11.719190 IP 172.253.195.196.35276 > 133.242.64.100.53: 45639% [1au] A? tdd-modeling.example.com. (61) 07:25:11.719195 IP 172.253.217.12.40587 > 133.242.64.100.53: 62658% [1au] A? web.modeling.example.com. (61) 07:25:11.719197 IP 172.253.9.4.50295 > 133.242.64.100.53: 37961% [1au] A? co.awarded.example.com. (59) 07:25:11.719224 IP 172.71.29.39.30489 > 133.242.64.100.53: 2496 [1au] A? SfaaSobvioUs.ExamplE.Com. (61) 07:25:11.719235 IP 209.66.107.33.57264 > 133.242.64.100.53: 50511 [1au] A? hap.modeling.example.com. (61) 07:25:11.719275 IP 96.114.53.69.53157 > 133.242.64.100.53: 5679 [1au] A? gitcn-awarded.example.com. (62) 07:25:11.719312 IP 172.70.229.30.59530 > 133.242.64.100.53: 45890 [1au] A? ipafoster.example.com. (58) 07:25:11.719336 IP 172.217.46.78.59507 > 133.242.64.100.53: 60186% [1au] A? testcloud-modeling.example.com. (67) 07:25:11.719351 IP 69.47.193.166.52891 > 133.242.64.100.53: 238 [1au] A? bfmpassing.example.com. (59) 07:25:11.719353 IP 34.218.119.91.26001 > 133.242.64.100.53: 31511% [1au] A? signal-modeling.example.com. (64) 07:25:11.719365 IP 34.218.119.91.13381 > 133.242.64.100.53: 4210% [1au] A? pairfiling.example.com. (59)

Slide 10

Slide 10 text

実際の攻撃(tcpdump) 07:25:11.719035 IP 209.216.160.2.50051 > 133.242.64.100.53: 43104 A? meetmodeling.example.com. (50) 07:25:11.719057 IP 205.171.30.238.44916 > 133.242.64.100.53: 64321% [1au] A? _.modeling.example.com. (71) 07:25:11.719069 IP 172.70.109.31.63292 > 133.242.64.100.53: 40380 [1au] A? osaExpe1-pLatINUM.exAmpLe.cOm. (66) 07:25:11.719071 IP 3.139.136.204.44597 > 133.242.64.100.53: 32383% [1au] A? webdirect.foster.example.com. (65) 07:25:11.719113 IP 18.188.77.103.42513 > 133.242.64.100.53: 14853 [1au] A? note-modeling.example.com. (62) 07:25:11.719132 IP 172.70.33.19.27971 > 133.242.64.100.53: 35379 [1au] A? indian-awarded.example.com. (63) 07:25:11.719147 IP 12.121.89.48.43564 > 133.242.64.100.53: 23891 A? matchfiling.example.com. (49) 07:25:11.719156 IP 74.125.181.130.64517 > 133.242.64.100.53: 25285% [1au] A? xmL.mODeLING.eXaMple.CoM. (61) 07:25:11.719166 IP 165.225.41.202.17203 > 133.242.64.100.53: 53044% [1au] A? qatawarded.example.com. (59) 07:25:11.719176 IP 96.114.53.67.20082 > 133.242.64.100.53: 41999 [1au] A? netherlands.filing.example.com. (67) 07:25:11.719190 IP 172.253.195.196.35276 > 133.242.64.100.53: 45639% [1au] A? tdd-modeling.example.com. (61) 07:25:11.719195 IP 172.253.217.12.40587 > 133.242.64.100.53: 62658% [1au] A? web.modeling.example.com. (61) 07:25:11.719197 IP 172.253.9.4.50295 > 133.242.64.100.53: 37961% [1au] A? co.awarded.example.com. (59) 07:25:11.719224 IP 172.71.29.39.30489 > 133.242.64.100.53: 2496 [1au] A? SfaaSobvioUs.ExamplE.Com. (61) 07:25:11.719235 IP 209.66.107.33.57264 > 133.242.64.100.53: 50511 [1au] A? hap.modeling.example.com. (61) 07:25:11.719275 IP 96.114.53.69.53157 > 133.242.64.100.53: 5679 [1au] A? gitcn-awarded.example.com. (62) 07:25:11.719312 IP 172.70.229.30.59530 > 133.242.64.100.53: 45890 [1au] A? ipafoster.example.com. (58) 07:25:11.719336 IP 172.217.46.78.59507 > 133.242.64.100.53: 60186% [1au] A? testcloud-modeling.example.com. (67) 07:25:11.719351 IP 69.47.193.166.52891 > 133.242.64.100.53: 238 [1au] A? bfmpassing.example.com. (59) 07:25:11.719353 IP 34.218.119.91.26001 > 133.242.64.100.53: 31511% [1au] A? signal-modeling.example.com. (64) 07:25:11.719365 IP 34.218.119.91.13381 > 133.242.64.100.53: 4210% [1au] A? pairfiling.example.com. (59) • ランダムな⽂字列、単語の組み合わせ • ⼤⽂字・⼩⽂字まざり(Google Public DNS仕様) • ラベル数が増えることも

Slide 11

Slide 11 text

なぜ「⽔責め攻撃」が有効か • キャッシュに存在しない⼤量のクエリ • DNSサーバ側でのDNSリクエストのパケット処理が必要となる • さくらのクラウド DNSアプライアンスではお客様のDNSレコードを管理し やすくするため PowerDNSおよびRDBMS(MySQL/MariaDB) をDNSサー バのバックエンドとして利⽤ • DNSサーバが持つキャッシュにヒットしないため都度SQLが発⾏ • CPU負荷によりサービスに影響

Slide 12

Slide 12 text

「⽔責め攻撃」への対策 • 存在するレコードへのクエリ以外はDNSサーバの⼿前でフィルタする • dnsdist(https://dnsdist.org/) というDNSのProxyサーバを導⼊ ✅ • 新たなドメインに対する⽔責め攻撃の対策にはならず • 攻撃を受け切るパフォーマンス💪 • スケールアウト/スケールアップ/負荷分散/チューニング • RDBMS を使うバックエンドをやめる🌸

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

DNS⽔責め攻撃に対する DNSサーバのパフォーマンスを測る

Slide 16

Slide 16 text

(権威)DNSサーバのベンチマークツール • dnsperf • https://github.com/DNS-OARC/dnsperf • あらかじめ問い合わせするクエリをファイルに記述しベンチマークを⾏う • ⾃作する • ランダム⽂字列の⼤⽂字⼩⽂字、⻑さや数を柔軟に変更したい • ベンチマーカから対象まで全体を把握できる。DNSについて学べる

Slide 17

Slide 17 text

作りました

Slide 18

Slide 18 text

3/10に実⾏

Slide 19

Slide 19 text

package main import ( "fmt" "math/rand" "net" "time" ) const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func randString(n int) string { rand.Seed(time.Now().UnixNano()) b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] } return string(b) } func main() { for i := 0; i < 1000; i++ { randStr := randString(10) // 10จࣈͷϥϯμϜจࣈྻΛੜ੒ addrs, err := net.LookupHost(randStr) if err != nil { fmt.Printf("Failed to resolve %s: %v\n", randStr, err) } else { fmt.Printf("%s resolves to %v\n", randStr, addrs) } } } 3/10に実⾏

Slide 20

Slide 20 text

package main import ( "fmt" "math/rand" "net" "time" ) const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func randString(n int) string { rand.Seed(time.Now().UnixNano()) b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] } return string(b) } func main() { for i := 0; i < 1000; i++ { randStr := randString(10) // 10จࣈͷϥϯμϜจࣈྻΛੜ੒ addrs, err := net.LookupHost(randStr) if err != nil { fmt.Printf("Failed to resolve %s: %v\n", randStr, err) } else { fmt.Printf("%s resolves to %v\n", randStr, addrs) } } } ちゃんと動きそう..?

Slide 21

Slide 21 text

コードの問題点を中の⼈に確認してみた • このコードを利⽤してDNSサーバの性能評価を⾏う場合、以下の問題点が考えられます。 1. このコードは、⼀つのプログラムから直列でDNSリクエストを送信するため、実際のユ ーザーからのリクエストとは異なる負荷がかかる可能性があります。実際のユーザーか らのリクエストは、同時に多数のリクエストが発⽣することがありますが、このコード では⼀つずつリクエストが発⽣するため、DNSサーバの性能評価としては適切ではあり ません。 2.このコードは、名前解決の際にエラーが発⽣した場合に、そのエラーを表⽰するだけで 処理を続⾏します。しかし、実際のユーザーからのリクエストでは、エラーが発⽣した 場合には適切にエラー処理を⾏う必要があります。 3/16に実⾏(⼀部抜粋)

Slide 22

Slide 22 text

ベンチマーカの必要条件 • 多数のリクエストを⾏うパフォーマンス 💪 • 適切なエラーが取得可能であること🔍

Slide 23

Slide 23 text

適切なエラー処理 • どこでエラーが起きたのか明らかにする • ベンチマークはテストでもある • 意図した失敗を許容できる • DNSで起きるエラー • Timeout (Drop含む) • NXDOMAIN: ドメインが⾒つからない • SERVFAIL: エラーの発⽣ • REFUSED: 拒否

Slide 24

Slide 24 text

エラー処理(net.Resolver) func do() { r := &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { d := net.Dialer{ Timeout: timeout, } return d.DialContext(ctx, protocol, net.JoinHostPort(host, port)) }, } sub := randString(10) _, err := r.LookupHost(context.Background(), sub+"."+zone) if err := err.(*net.DNSError); err.IsNotFound { } } エラーは取得できるが、詳しい内容はエラーの⽂⾔を解析しないとわからない (LookupHostはAと同時にAAAAの名前解決を⾏う)

Slide 25

Slide 25 text

エラー処理(miekg/dnsの) import "github.com/miekg/dns" atype = dns.StringToType["A"] func do() { c := &dns.Client{Net: protocol, Timeout: timeout} address := net.JoinHostPort(host, port) m := new(dns.Msg) sub := randString(10) m.SetQuestion(sub+”.”+zone+”.", atype) r, _, err := c.Exchange(m, address) if err != nil { } if r.Rcode == dns.RcodeRefused { } } Pure GoなシンプルなAPIとコード DNSのステータス(RCODE)が取得可能

Slide 26

Slide 26 text

パフォーマンス💪 • 対象のパフォーマンス > ベンチマーカのパフォーマンスでは正しい計測ができない • 現代において Apache Bench が常に適切なツールとはならない • クライアント側にもハイパフォーマンスが求められる • ベンチマークにとって余分な処理の削減(Happy EyeBallsなど) • 並⾏・並列化 • パフォーマンスチューニング

Slide 27

Slide 27 text

並⾏・並列化 Use Goroutines ctx, cancel := context.WithTimeout( context.Background(), timeLimit, ) defer cancel() for w := 0; w < maxWorkers; w++ { go func() { for { do() } }() } <- ctx.Done() import "github.com/miekg/dns" atype = dns.StringToType["A"] func do() { c := &dns.Client{Net: protocol, Timeout: timeout} address := net.JoinHostPort(host, port) m := new(dns.Msg) sub := randString(10) m.SetQuestion(sub+”.”+zone+”.", atype) r, _, err := c.Exchange(m, address) if err != nil { } if r.Rcode == dns.RcodeRefused { } } goroutineを起動し、 名前解決をループで実⾏

Slide 28

Slide 28 text

Goのパフォーマンスチューニング • メモリーアロケーションしない • 計測をしましょう • 最後のGOGC

Slide 29

Slide 29 text

メモリーアロケーションを減らす import "github.com/miekg/dns" ctx, cancel := context.WithTimeout( context.Background(), timeLimit, ) defer cancel() for w := 0; w < maxWorkers; w++ { go func() { c := &dns.Client{Net: protocol, Timeout: timeout} m := new(dns.Msg) address := net.JoinHostPort(host, port) for { do(c, m, address) } }() } <- ctx.Done() var atype = dns.StringToType[“A"] func do(c *dns.Client, m *dns.Msg, address string ) { sub := randString(10) m.SetQuestion(sub+”.”+zone+”.", atype) r, _, err := c.Exchange(m, address) if err != nil { } if r.Rcode == dns.RcodeRefused { } } ループごとに初期化する 必要ない。ループの外へ移動する

Slide 30

Slide 30 text

⽂字列連結の⾼速化 • Go⾔語では⽂字列はImmutable • ⽂字列の連結は新しい⽂字列をアロケーションする zone := randString(10) zone += “.” zone += “example.com" zone += “.” m.SetQuestion(zone, atype)

Slide 31

Slide 31 text

⽂字列連結の⾼速化 バッファの再利⽤ import ( “strings" “github.com/miekg/dns" ) for w := 0; w < maxWorkers; w++ { go func() { c := &dns.Client{Net: protocol, Timeout: timeout} m := new(dns.Msg) address := net.JoinHostPort(host, port) bb := new(bytes.Buffer) for { do(c, m, address, bb) } }() } <- ctx.Done() var atype = dns.StringToType[“A"] func do(c *dns.Client, m *dns.Msg, address string bb *bytes.Buffer ) { defer sb.Reset() randString(bb, 10) bb.WriteByte('.') bb.WriteString(opt.Zone) bb.WriteByte('.') b := sb.Byte() z := unsafe.String(&b[0], len(b)) m.SetQuestion(z, atype) r, _, err := c.Exchange(m, address) } ループの外でbufferを作成し ループの中でresetして使い回す

Slide 32

Slide 32 text

ランダム⽂字列の⽣成 // https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go const ( letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789" letterIdxBits = 6 // 6 bits to represent a letter index letterIdxMask = 1<= 0; { if remain == 0 { cache, remain = int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { bb.WriteByte(letterBytes[idx]) i-- } cache >>= letterIdxBits remain-- } } 新たな⽂字列を⽣成することなく ランダムな⽂字を追記していく

Slide 33

Slide 33 text

チューニングの結果の計測 % go test -bench . -benchmem -benchtime 3s goos: darwin goarch: amd64 pkg: github.com/kazeburo/bench cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz BenchmarkDo-8 2798367 1269 ns/op 28 B/op 3 allocs/op BenchmarkDoMoto-8 324367 10319 ns/op 224 B/op 8 allocs/op PASS ok github.com/kazeburo/bench 8.465s 9倍程度⾼速化 (dns.SetQuestionまで) 1回あたりのアロケー ション回数(allocs/ op) 1回あたりのアロ ケーションで確保 した容量(B/op) 実⾏した回数 1回あたりの実⾏に掛か った時間(ns/op)

Slide 34

Slide 34 text

チューニングの結果の計測 % go test -bench . -benchmem -benchtime 3s goos: darwin goarch: amd64 pkg: github.com/kazeburo/bench cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz BenchmarkDo-8 2798367 1269 ns/op 28 B/op 3 allocs/op BenchmarkDoMoto-8 324367 10319 ns/op 224 B/op 8 allocs/op PASS ok github.com/kazeburo/bench 8.465s 9倍程度⾼速化 (dns.SetQuestionまで) 1回あたりのアロケー ション回数(allocs/ op) 1回あたりのアロ ケーションで確保 した容量(B/op) 実⾏した回数 1回あたりの実⾏に掛か った時間(ns/op)

Slide 35

Slide 35 text

プロファイリングを⾏う go test -cpuprofile cpu.prof -memprofile mem.prof -benchtime 3s -bench 'BenchmarkDo$' goos: darwin goarch: amd64 pkg: github.com/kazeburo/bench cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz BenchmarkDo-8 2401633 1478 ns/op PASS ok github.com/kazeburo/bench 5.362s

Slide 36

Slide 36 text

プロファイリング % go tool pprof -top mem.prof Type: alloc_space Time: Mar 13, 2023 at 4:08pm (JST) Showing nodes accounting for 99.73MB, 100% of 99.73MB total flat flat% sum% cum cum% 87MB 87.24% 87.24% 98MB 98.27% github.com/miekg/dns.(*Msg).SetQuestion 10.50MB 10.53% 97.77% 11MB 11.03% github.com/miekg/dns.id 1.16MB 1.16% 98.93% 1.16MB 1.16% runtime/pprof.StartCPUProfile 0.57MB 0.57% 99.50% 0.57MB 0.57% compress/flate.newDeflateFast (inline) 0.50MB 0.5% 100% 0.50MB 0.5% encoding/binary.Read 0 0% 100% 0.57MB 0.57% compress/flate.(*compressor).init 0 0% 100% 0.57MB 0.57% compress/flate.NewWriter 0 0% 100% 0.57MB 0.57% compress/gzip.(*Writer).Write 0 0% 100% 98MB 98.27% github.com/kazeburo/bench.BenchmarkDo 0 0% 100% 98MB 98.27% github.com/kazeburo/bench.do 0 0% 100% 1.16MB 1.16% main.main 0 0% 100% 1.16MB 1.16% runtime.main … このあたりがアヤシイ

Slide 37

Slide 37 text

dns.SetQuestion Hack • 問い合わせを格納している配列を都度⽣成している • 配列を使い回し、問い合わせ「名」のみ差し替え • uint16なランダムIdの⽣成 • ベンチマークなので uint16(atomic.AddUint64()) に変更

Slide 38

Slide 38 text

チューニングの確認 % go test -bench . -benchmem -benchtime 3s goos: darwin goarch: amd64 pkg: github.com/kazeburo/bench cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz BenchmarkDo-8 24056054 138.3 ns/op 0 B/op 0 allocs/op BenchmarkDoMoto-8 306056 10851 ns/op 224 B/op 8 allocs/op PASS ok github.com/kazeburo/bench 7.234s アロケーションは0に、当初の80倍程度⾼速化 1回あたりのアロケー ション回数(allocs/ op) 1回あたりのアロ ケーションで確保 した容量(B/op) 実⾏した回数 1回あたりの実⾏に掛か った時間(ns/op)

Slide 39

Slide 39 text

GOGC • GoでGCを実⾏するヒープサイズの⽬標となるパラメータ • デフォルト 100 で⼤きくすることでGCの頻度を減らしCPUコストを下げる (メモリは増加する) # ./prsd-bench -P 53 -H 192.168.10.50 --time-duration 32s --max-workers 300 --max-length 8 --label 1 -- zone x.y 2023-03-11 18:18:56.526777432 +0900 JST m=+20.001327490 resolved: 0.000000 query/sec, refused 157012.900000 query/sec, failed 0.000000 query/sec # GOGC=500 ./prsd-bench -P 53 -H 192.168.10.50 --time-duration 32s --max-workers 300 --max-length 8 -- label 1 --zone x.y 2023-03-11 18:19:40.175067406 +0900 JST m=+20.001772051 resolved: 0.000000 query/sec, refused 178251.500000 query/sec, failed 0.000000 query/sec 最後の⼀押として使う

Slide 40

Slide 40 text

まとめ • ⽔責め攻撃はやっかい • ベンチマーカは適切な情報(エラー)が取得可能であること • ベンチマークはテストでもある • ベンチマーク対象に負けないパフォーマンス 💪 • Go⾔語で作る上でのパフォーマンスチューニング

Slide 41

Slide 41 text

SAKURA internet ࣾձΛࢧ͑Δ 
 ύϒϦοΫΫϥ΢υΛ 
 Ұॹʹ࡞Γ·ͤΜ͔ʁ Perl, Go, Python インフラ基盤から フロントエンドまで 採⽤強化中! さくらインターネットではエン ジ ニア採⽤を強化しています さくらインターネットは新たなアイ デ アの創出に強い熱意と情熱を持って挑戦するお客様を は じ め、私たちとつな が りのあるす べ ての⼈たちのために、未来のある べ き姿を想い描きな が ら ―「やりたいこと」を「 で きる」に変える ― あらゆるア プ ローチを “インターネッ ト”を通 じ て提供します。 詳しくはWebサイトにて、カジュアル⾯談もやってます 👉 www.sakura.ad.jp/lp/22engineer/

Slide 42

Slide 42 text

ご清聴ありがとう ございました このあとはさくらインターネットのブースにおります。 質問などありましたらお気軽に~