TECH PLAY

KINTOテクノロジーズ

KINTOテクノロジーズ の技術ブログ

969

こんにちは!KINTOテクノロジーズで生成AIエバンジェリストをしている和田( @cognac_n )です。 皆さんはプロンプトの管理をどのように行なっていますか?今回はプロンプトの 作成/編集 、 動作確認 、 実装 、 管理 が簡単にできるPromptyを紹介します! 1. Promptyとは? Promptyは、大規模言語モデル(LLM)を使用する際のプロンプトを効率的に開発するためのツールです。YAML形式でプロンプトとパラメータを一元管理でき、GitHubなどバージョン管理ツールでのプロンプト管理や、チームでの開発にもピッタリです。Visual Studio Code (以下 VS Code)の拡張機能を使用することで、プロンプトエンジニアリングの作業効率を大幅に向上させることができます。 Prompty導入のメリット Azure AI Studioとの連携やPrompt Flowとの連携も魅力的ですが、今回はVS Codeとの連携を中心に紹介していきます。 Promptyはこんな人におすすめ! プロンプト開発を高速化したい プロンプトのバージョン管理が必要 チームでプロンプト開発をしている プロンプトを実行するアプリ側の記述をシンプルにしたい https://github.com/microsoft/prompty 2. 前提条件 必要な前提条件(執筆時点) Python 3.9以上 VS Code(拡張機能を使用する場合) OpenAI APIキーまたはAzure OpenAI Endpoint(使用するLLMに応じて) インストール手順と初期設定 VS Codeの拡張機能をインストールしましょう https://marketplace.visualstudio.com/items?itemName=ms-toolsai.prompty pipなどを用いてライブラリをインストールしましょう pip install prompty https://pypi.org/project/prompty/ 3. 実際に使ってみる 3-1. Promptyファイルを新規作成 エクスプローラータブで右クリック→「New Prompty」を選択することで、雛形が作成されます。 ![New Prompty](/assets/blog/authors/s.wada/20240821/image_2.png =350x) New Prompty 作成される雛形は以下の内容です。 --- name: ExamplePrompt description: A prompt that uses context to ground an incoming question authors: - Seth Juarez model: api: chat configuration: type: azure_openai azure_endpoint: ${env:AZURE_OPENAI_ENDPOINT} azure_deployment: <your-deployment> parameters: max_tokens: 3000 sample: firstName: Seth context: > The Alpine Explorer Tent boasts a detachable divider for privacy, numerous mesh windows and adjustable vents for ventilation, and a waterproof design. It even has a built-in gear loft for storing your outdoor essentials. In short, it's a blend of privacy, comfort, and convenience, making it your second home in the heart of nature! question: What can you tell me about your tents? --- system: You are an AI assistant who helps people find information. As the assistant, you answer questions briefly, succinctly, and in a personable manner using markdown and even add some personal flair with appropriate emojis. # Customer You are helping {{firstName}} to find answers to their questions. Use their name to address them in your responses. # Context Use the following context to provide a more personalized response to {{firstName}}: {{context}} user: {{question}} --- で挟まれた領域に、パラメータを記述します。その下に、プロンプト本体を記述します。 system: や user: を用いることでroleの定義を行うことができます。 基本的なパラメータ紹介 パラメータ 説明 name プロンプトの名称を記述します description プロンプトの説明を記述します authors プロンプト作成者の情報を記述します model プロンプトで使用する生成AIモデルの情報を記述します sample プロンプトに {{context}} などのプレースホルダーがある場合、ここに記述した内容が動作確認時に代入されます 3-2. APIキーやパラメータなどの設定 APIの実行に必要なAPIキーやエンドポイントの情報、実行時のパラメータ設定を行う方法がいくつかあります。 【パターン1】 .promptyファイルへの記述 .promptyファイルにそのまま記述する方法です。 model: api: chat configuration: type: azure_openai azure_endpoint: ${env:AZURE_OPENAI_ENDPOINT} azure_deployment: <your-deployment> parameters: max_tokens: 3000 ${env:AZURE_OPENAI_ENDPOINT} のように環境変数を参照させることもできます。ただし、 azure_openai_api_key をこの方法で設定することはできません。 ![azure_openai_api_keyは.promptyファイルに記述できない](/assets/blog/authors/s.wada/20240821/image_3.png =750x) azure_openai_api_keyは.promptyファイルに記述できない 【パターン2】 settings.jsonを使った設定 VS Codeの settings.json を用いる方法です。設定が不足している状態で画面右上の再生ボタンをクリックすると、settings.jsonの編集に誘導されます。defaultの定義以外にconfigを複数作成でき、これらを切り替えながら動作確認をすることが可能です。 type を azure_openai としているときに api_key を空に設定して実行すると、後述するAzure Entra IDでの認証に誘導されます。 { "prompty.modelConfigurations": [ { "name": "default", "type": "azure_openai", "api_version": "2023-12-01-preview", "azure_endpoint": "${env:AZURE_OPENAI_ENDPOINT}", "azure_deployment": "", "api_key": "${env:AZURE_OPENAI_API_KEY}" }, { "name": "gpt-3.5-turbo", "type": "openai", "api_key": "${env:OPENAI_API_KEY}", "organization": "${env:OPENAI_ORG_ID}", "base_url": "${env:OPENAI_BASE_URL}" } ] } 【パターン3】 .envを使った設定 .env ファイルを作成しておくことで、そこから環境変数を読み取ってくれます。 .env ファイルは使用する .prompty ファイルと同じ階層に配置する必要があるので注意です。手元で試す際にはとてもお手軽な設定方法です。 AZURE_OPENAI_API_KEY=YOUR_AZURE_OPENAI_API_KEY AZURE_OPENAI_ENDPOINT=YOUR_AZURE_OPENAI_ENDPOINT AZURE_OPENAI_API_VERSION=YOUR_AZURE_OPENAI_API_VERSION 【パターン4】 Azure Entra IDを用いた設定 適切な権限が割り当てられたAzure Entra IDでログインすることで、APIの利用が可能です。 私はまだ試せていません 3-3. VS Codeでのプロンプト実行 右上の再生ボタンから簡単にプロンプトを実行することができます。結果は 出力(OUTPUT) に表示されます。 出力 内のドロップダウンから「Prompty Output(Verbose)」を選択すると、結果の生データを確認できます。プレースホルダーへの代入状況や token usage など細かな情報を確認したい場合に便利です。 右上の再生ボタンでプロンプトが実行できます 結果は出力(OUTPUT)で確認できます 3-4. その他のパラメータ紹介 以下のページで様々なパラメータが紹介されています。 https://prompty.ai/docs/prompty-file-spec inputs や、jsonモードを使用する際の outputs などはプロンプトの可視性が上がるので是非定義しましょう。 inputs: firstName: type: str description: The first name of the person asking the question. context: type: str description: The context or description of the item or topic being discussed. question: type: str description: The specific question being asked. 3-5. アプリケーションへの組み込み アプリケーションで使用しているライブラリによって記法が異なります。Prompty自体のバージョンアップも盛んなため、常に最新のドキュメントを確認するようにしましょう。参考までに、 Prompt Flow との組み合わせで使用したコードを記載します。シンプルな記述でプロンプトの実行が可能です。 from promptflow.core import Prompty, AzureOpenAIModelConfiguration # AzureOpenAIModelConfiguration を使って Prompty をロードするための設定を行う configuration = AzureOpenAIModelConfiguration( azure_deployment="gpt-4o", # Azure OpenAI のデプロイメント名を指定 api_key="${env:AZURE_OPENAI_API_KEY}", # APIキーを環境変数から取得 api_version="${env:AZURE_OPENAI_API_VERSION}", # APIバージョンを環境変数から取得 azure_endpoint="${env:AZURE_OPENAI_ENDPOINT}", # Azureエンドポイントを環境変数から取得 ) # モデルのパラメータを上書きするための設定を行う # サンプルとしてmax_tokensを上書きしています override_model = {"configuration": configuration, "max_tokens": 2048} # 上書きされたモデル設定を使ってPromptyをロード prompty = Prompty.load( source="to_your_prompty_file_path", # 使用するPromptyファイルを指定 model=override_model # 上書きされたモデル設定を適用 ) # promptyを実行 result = prompty( firstName=first_name, context=context, question=question ) # 与えられたテキストを元にPromptyを実行し結果を取得 4. まとめ Promptyは、プロンプトエンジニアリングの作業を大幅に効率化できる強力なツールでした!特に、VS Codeと連携した開発環境は、プロンプトの 作成 、 動作確認 、 実装 、 管理 までをシームレスに行えるため、非常に使いやすいです。Promptyを使いこなすことで、プロンプトエンジニアリングの効率と品質を大幅に向上させることができると思います。ぜひ、皆さんも試してみてください! Prompty導入のメリット(再掲) We Are Hiring! KINTOテクノロジーズでは、事業における生成AIの活用を推進する仲間を探しています。まずは気軽にカジュアル面談からの対応も可能です。少しでも興味のある方は以下のリンク、または XのDM などからご連絡ください。お待ちしております!! https://hrmos.co/pages/kinto-technologies/jobs/1955878275904303115 弊社における生成AIの取り組みについてはこちらで紹介しています。 https://blog.kinto-technologies.com/posts/2024-01-26-GenerativeAIDevelopProject/ ここまでお読みいただき、ありがとうございました!
アバター
Introduction Hello, I am nam. I joined the company in November! I interviewed those who joined the company in February and March 2024 immediately after joining, and have summarized their impressions in this article.. I hope this content will be useful for those who are interested in KINTO Technologies, and serve as a reflection for the members who participated in the interview. J.O ![alt text](/assets/blog/authors/nam/newcomers/icon-jo.jpg =250x) Introduction I am J.O., and I joined the company in March. I am a producer at KINTO ONE Development Division New Vehicle Subscription Development Group. In my previous job, I was involved in the planning and operation of web/apps for our toC services at an operating company, and summarizing the definition of development requirements on the business side. How is your team structured? The New Vehicle Subscription G involves various teams, including back-end, front-end, content development, and tool development. With over 40 members, including our service providers, it is one of the largest departments in the company What was your first impression when you joined KTC? Any surprises? I felt that the expectations for KTC were higher than I had imagined before joining the company, such as the relationship with group companies, the position of the company, and the role required to provide a new platform in the mobility industry. What is the atmosphere like on site? Although the company is centered around engineers and has a quiet atmosphere, it is also quite friendly. Slack chats and the emojis used make it also lively. You can tell it's a vehicle-related company, as many people decorate their desks with car models. How did you feel about writing a blog post? Even though I had the opportunity to create website content in my previous job, this is my first time sharing about myself, so I’m feeling nervous. Question from S.A. “What surprised or impressed you about joining KTC?” There are a lot of events such as study sessions. At least once every two weeks, some sort of event is held, and I was impressed by the proactive approach to receiving and sharing new information. nam ![alt text](/assets/blog/authors/nam/newcomers/icon-nam.JPG =250x) Introduction I joined KTC in February. I’m nam. I was a front-end engineer at a production company in my previous role. How is your team structured? I had the impression that it was a small team and the responsibilities were clearly divided among everyone. What was your first impression when you joined KTC? Any surprises? Orientation was very warm. I felt a strong message that all of us are moving forward in the same direction. What is the atmosphere like on site? There are team members nearby who are working on the same project, so I feel that everyone is working freely while consulting with each other. It was my first time working in a large office, so I had imagined that only the sound of the keyboard would resonate through the large space, but that was not the case, so I was relieved. How did you feel about writing a blog post? I’ve been reading this Tech Blog since before I joined the company, and now that I’m finally on the writing side, I’m feeling nervous. Question from J.O. What aspects make a website stand out to you from a front-end engineer’s perspective? I have a background in design, so I find the websites where design and technology are in harmony to be truly amazing. I believe that a great site is one that is well made. Sometimes there are sites where it's hard to imagine how much discussion goes from the planning stage, how engineers and designers communicate with each other, and how they understand each other's fields. So I think, "Wow, how robust, great!" when I see a harmonious site that excels technically and in design. KunoTakaC ![alt text](/assets/blog/authors/nam/newcomers/icon-kuno-takac.jpg =250x) Introduction I am Kuno from the KTC Management Department. I am in charge of Labor Systems in general (such as SmartHR, Recoru, Raku-ro, Kaonavi, etc.). My previous job was as an SE in a factory, and before that, I worked as a handyman (mainly handling infrastructure for small and medium-sized enterprises.). In 2023, I was classified as having a level 4 disability (lower limb paralysis), but there is no need for any special care. I usually have a cane so it's easy to recognize me, but please remember my face so you can still recognize me even when don't have it! How is your team structured? There are 11 people in the Management Department, out of which 2 of us are in the KTC Management Department. But in Nagoya... I’m the only one! Please rest assured that we are getting along well. What was your first impression when you joined KTC? Any surprises? As an IT company, I thought I would talk to the Management Department via some kind of system, but I was a little surprised that we talk face-to-face using a conference room. - ** What is the atmosphere like on site?** The office is generally quiet, but it’s easy to communicate, and we often have discussions together. Since the Management Department has open seating, it is convenient to choose a seat near the person you want to talk to. How did you feel about writing a blog post? I thought it would be helpful to mention that I created the #lumbar-care channel on Slack. I also additional jobs besides labor systems, so it is a little difficult. Question from nam "It has been 3 months since you joined the company. Have you noticed anything different from your previous job, or something unique to KTC? " To put it in one word, quiet. In my previous job, it was like a live house every day, with the noise of air conditioning in the server room resembling a jet engine, the vibration of machine tools that felt like an earthquake, the sounds of drum-like impact printers and electronic printers, and the warning tones of three-color patrol lights, all punctuated by telephones and SystemWalker alerts. There was only an on-premise environment In my previous job, and I was exposed to SaaS for the first time. on-premises solutions can be preferable sometimes, while other times SaaS are better. I’ve realized that each has its own pros and cons. M ![alt text](/assets/blog/authors/nam/newcomers/icon-m.jpg =250x) Introduction I decided to dive into a new environment because I wanted to take on the challenge of developing in-house products, which was difficult to experience in my previous job. How is your team structured? Our team develops a product to support car sales recommendations at dealerships, making them more efficient and advanced. We have a tech lead, front-end engineers, and back-end engineers. What was your first impression when you joined KTC? Any surprises? Before joining the company, I had the impression that it was a “mature startup,” so I thought I would be required to be autonomous and self-driven from the first day. However, I was a little surprised that the onboarding was thorough and took time, including hands-on training and dialogues with the president. Thanks to this, it helped me quickly learn about new domain knowledge and get to know the executives. What is the atmosphere like on site? In my development team, we work on multiple product developments side by side. To stay informed about each other’s tasks, we share information in our daily morning meetings, where each team member talks about what they are focusing on. I think we communicate frequently and chat casually when we are in the office. How did you feel about writing a blog post? I have never had the opportunity to post information on a blog, so it feels fresh and exciting. Question from KunoTakaC "What is your favorite storage solution? Please suggest something practical!" If you are having trouble keeping your phone and PC charging cables tidy on your desk or floor, try the cheero CLIP Multi-Purpose Clip ! It’s magnetic, so it’s super easy to attach and remove, letting you quickly tie up any messy cables. Plus, it’s flexible like a wire and keeps its shape, so you can even use it as a stand for your phone to watch videos! R.S ![alt text](/assets/blog/authors/NAM/newcomers/icon-rs.jpg =250x) Introduction I am R.S from KINTO ONE Development Division New Car Subscription Group. I am in charge of the front-end of KINTO ONE. How is your team structured? There are six people on our team. What was your first impression when you joined KTC? Any surprises? I was surprised by the high level of flexibility in work styles. As a working parent, the full-flex work schedule is incredibly helpful. What is the atmosphere like on site? In our weekly planning, we clarify individual tasks and proceed with our work diligently. How did you feel about writing a blog post? I didn’t expect to write it so soon, but after my first post, I became more aware of our company's blog. Question from M "How do you catch up when trying something new? Any learning tips?" When something interests me, I take a step forward and give it a try. I tend to explore broadly rather than deeply, diving into new challenges. Sometimes, past experiences in completely different areas connect unexpectedly, and I find that moment fascinating. Hanawa ![alt text](/assets/blog/authors/NAM/newcomers/icon-hanawa.jpg =250x) Introduction I am Hanawa, a front-end engineer at KINTO ONE New Vehicle Subscription Development Group. I mainly worked as a front-end engineer in my previous job. I would like to utilize the knowledge and experience I have gained so far in my work and improve my technical capabilities regardless of field. How is your team structured? We have 6 people on our front-end team. What was your first impression when you joined KTC? Any surprises? I was impressed by how generous the employee benefits are. What is the atmosphere like on site? Everyone is highly sensitive to technical updates and has the ability to communicate, so it is stimulating. I think it’s an environment where we can easily make suggestions. It appears that there are actual cases where services were developed from engineers’ ideas, so I have the impression that this kind of atmosphere is cultivated throughout the company. How did you feel about writing a blog post? I haven’t really shared much before, so I thought it was a great opportunity. I would like to write something about tech topics in my post, not just for this company’s entry. Question from R.S "What has changed significantly since joining the company? " Compared to my previous job, we have a much larger engineering organization here. (There were five engineers in my previous job.) To be honest, I haven’t fully understood who is working on which products yet. Various events such as study sessions are held regularly across departments, so I hope to participate in them to deepen my understanding. Taro ![alt text](/assets/blog/authors/nam/newcomers/icon-taro.jpg =250x) Introduction I am Taro, and I joined KTC Creative Group. How is your team structured? We are a team of 9 directors and designers. What was your first impression when you joined KTC? Any surprises? I felt a strong sense of teamwork during the orientation for new employees with the message, "We will work together as One Team and move forward." What is the atmosphere like on site? The team members are cheerful, friendly and very creative. Thanks to active communication, we constantly exchange opinions and ideas, creating a stimulating work environment. How did you feel about writing a blog post? I thought, "Oh, it’s that thing I read in the past logs of the Tech Blog." Question from Hanawa "What do you prioritize the most in your day-to-day work?" The "current situation and goals" in terms of "issues, needs, and value." S.A. ![alt text](/assets/blog/authors/NAM/newcomers/icon-sa.jpg =250x) Introduction I am S.A. and I joined the Data Analysis Division. How is your team structured? Including the leader and myself, we are 9 team members in total. What was your first impression when you joined KTC? Any surprises? I was impressed by how pleasantly relaxed the atmosphere was. What is the atmosphere like on site? Everyone has their own area of expertise, and it’s an inspiring environment. How did you feel about writing a blog post? I was nervous because it was my first time writing a blog, but I thought it was a good initiative. Question from Taro "It's been a month since you joined the company, but have you become more conscious of anything in your work? " I’m trying to keep up with the fast pace so I don't fall behind. Lastly Thank you all for sharing your thoughts after joining our company! At KINTO Technologies, we are continually welcoming new members every day! We look forward to sharing more onboarding entries from various divisions and team members. Moreover, KINTO Technologies is actively seeking professionals who can collaborate across different divisions and fields! For more information, click here !
アバター
はじめに 初めまして!モバイルアプリ開発グループでプロデューサーを担当しているHyugaです。 本日は面白いネタをひっさげ、初ブログ投稿をします!今、生成AIが話題となっておりますが、今回ご紹介するのは音楽生成AIです。この音楽生成AI、本当にすごいんです!端的にいうと、簡単な指示をするだけで、プロ顔負けの音楽生成ができてしまいます。しかも、伴奏だけではなく自然な発音での歌までつけてくれます。そして生成時間は数分程度。まさに神業です。自分が有名音楽クリエーターになった気分にもなれる最高の感覚を味わってください。 今回のネタは、この音楽生成AIを使って、勝手にKINTOテクノロジーズの社歌を作ってみた! というテーマでご紹介します! 音楽生成AI 「Suno AI」とは? 音楽生成AIはさまざま世に出ているのですが、私が活用したのはアメリカのSuno, Inc.というベンチャー企業で開発された「Suno AI」です。 Suno, Inc.は、元々KenshoというAIスタートアップで働いていた4人、Michael Shulman、Georg Kucsko、Martin Camacho、Keenan Freybergによって設立されました。 Suno AIは、2023年12月に初めてリリースされ、その後も継続的にアップデートされています。最新の安定版は2024年5月にリリースされたv3.5です。(2024年8月時点) Suno AIでの音楽生成手順を以下に簡単にまとめます。 なお、AIの進化スピードは凄まじいため、以下の流れは今後変わる可能性があります。 参考程度にご確認いただき、実際には最新の情報をご確認いただくことをおすすめします。 ウェブサイトにアクセス( https://suno.com/) Sunoの公式サイトにアクセスします。 ![](/assets/blog/authors/hyuga/20240814/sunoai1.png =630x) アカウント作成 無料アカウントを作成します​​。 Google、Discord、Microsoftなど各種アカウントで作成できます。 ![](/assets/blog/authors/hyuga/20240814/sunoai2.png =630x) 曲の作成セクションに移動 「Create」セクションに移動します​​。 テキストプロンプトの入力 曲の歌詞、スタイル(ジャンル)などテキストプロンプトを入力します​。 ![](/assets/blog/authors/hyuga/20240814/sunoai3.png =630x) 曲の生成 Suno AIが入力されたプロンプトに基づいて曲を生成します​​。 曲のダウンロード 必要に応じて、生成された曲をダウンロードしてさらに編集することができます。​ Suno AIを使ってオリジナル社歌を勝手に作ってみた(笑) さて、ここからはSuno AIの力をご紹介していきましょう! どうせデモンストレーションするなら面白い方が良いと思い、勝手にKINTOテクノロジーズの社歌を作ってしまうことにします! 作戦は以下のとおりです! KINTOテクノロジーズのマネージャー陣に会社の強みとは?のコメントをもらう! もらったコメントをキーワードにしてChatGPTに歌詞生成してもらう! 生成された歌詞をSuno AIにインプットして素敵な社歌を作ってもらう! こんな流れになるとは思うのですが、ここで一つ考えました・・・ うーん。面白みが足りない・・・ そうだ!自分で歌ってしまおう!!!(笑) ということで、生成された音楽から歌声をカットして、Hyuga自ら歌って披露する! を目標に進めることにします!!!(笑) テーマ変更 「Suno AIを使ってオリジナル社歌を勝手に作って、 勝手に歌ってみた(笑) 」 となります! マネージャー陣から届いたコメント 会社の強みについて、とても素敵なコメントがたくさん届きました! スピード感があり、魂さえあれば、誰もがチャレンジして、活躍することができる! 発信に積極的なエンジニアが多い モダンな開発環境 急拡大する開発組織 コミュニケーション好き、新しい技術への探究心がある、新旧のエンジニアがそれぞれいる 自分で手をあげれば色々取り組むことができる、全世界がスコープ、優秀な人材 新しいことにチャレンジしている などなど!ほんと素敵ですねー! ChatGPTにて生成された最高の歌詞! マネージャー陣から届いたコメントを丁寧にChatGPTに届け、素敵な歌詞を生成してもらいました! Yeah, yeah, Here we go! KINTOテクノロジーズ、Let’s go! スピード感、魂さえあれば 誰もがチャレンジできる、その可能性を感じて 発信に積極的、未来への道を切り拓く モダンな開発環境、革新の舞台を用意して We are the KINTOテクノロジーズ! コミュニケーション好きな仲間たち 新しい技術への探究心で 未来を築く、KINTOの道を行こう 魂があれば、どんな壁も乗り越えられる 夢を追いかけて、果てしない世界へ KINTOテクノロジーズ、Let’s Go! 未来を切り拓く、その先へと進もう... 急なリクエスト ここまで記事を書き進めていると... このテックブログの翻訳担当の方よりDMが... Hyugaさん、翻訳用にオリジナルソングの英語版作れませんか!? なんと!そうきましたか(笑) こんな素敵なリクエスト断る理由がありません! 考える余地もなく、即答で「できます!」と返信しました(笑) ただ、日本語版と同じテイストで作成しては面白くないと考え、 英語版ということで洋楽になるわけなので、少しお洒落な感じにできたらいいなっと考えました! そこでオリジナルソングは以下のジャンル指定で生成してみました。 日本語版:疾走感のある爽やかなロックソング 英語版:落ち着きがありながらも明るくなれるジャズソング さて、どんな完成になるのでしょうか!? 神曲ができてしまった(笑) 完成曲を聴いた瞬間、一筋の涙が...w あまりにも完成度が高い!高すぎる!! 最高のオリジナル社歌ができてしまったので、 前置きはここまでにして披露したいと思います!ぜひお聴きください!! まずは日本語バージョンです! https://www.youtube.com/watch?v=zmv06e8cTFI 続いて英語バージョンです! https://www.youtube.com/watch?v=tCIkXdUv9NA まとめ いかがでしたでしょうか?予想以上にクオリティの高い神曲になっていませんか!? 私の今後の目標はこのオリジナル社歌を正式な社歌に認めてもらうことです(笑) ぜひ、私と同じく感動の音楽生成AI体験をしてみてください。無料アカウントでも1日10曲生成できます。 1回の生成で2曲作られるので、無料チャレンジは5回です。AIが奏でる音の世界へ、一歩踏み出してみよう! 最後まで拝読いただき、ありがとうございました!
アバター
はじめに こんにちは。ご覧いただきありがとうございます! KINTO FACTORY (以下 FACTORY)という今お乗りのクルマをアップグレードできるサービスで、フロントエンド開発をしている中本です。 今回は、先日リリースしました FACTORY マガジン の記事コンテンツを、執筆・管理するために導入しました OSSのツール、 Strapi について紹介したいと思います。 Strapi とは Strapi とは、セルフホスト型のヘッドレスCMSで、SaaS 等で提供されている他のCMSサービスとは異なり、自身でサーバーやデータベース等の環境を用意し運用していくものになります。(Strapi には、クラウド環境も用意してくれる Strapi Cloud も用意されています。) KINTO では SaaS の CMSサービスを利用し、コラムなどの記事を掲載しているサービスがすでにいくつか有りましたが、運用面・コスト面の両立の難しさや、新しい機能追加や既存機能のカスタマイズ領域が少なかったりと課題感が分かってきたので、自前で用意することを視野に OSS CMS ツールの選定を行ってきました。 OSS のヘッドレスCMS と聞いて、よく耳にするのは Wordpress が有名かもしれませんが、他にも最近勢いのあるツールを調べたところ、Strapi について知ることとなりました。 様々なOSSツールの中で、我々が重視したポイントは以下のようになります。 使いやすさ :管理UIが直感的で、開発者とコンテンツ管理者の両方に優しい設計 コミュニティサポート :大規模なコミュニティによるサポートと豊富なドキュメント 豊富なプラグイン :多様なプラグインが提供されており、簡単に機能を拡張できる スケーラビリティ :Node.jsベースのため、高いパフォーマンスとスケーラビリティを実現 もし足りない機能や我々の使い方に合わない部分が出てきた場合でも、プラグインの更新・作成などは Java Script の知識があれば簡単に行えそうで、導入コストが低そうな事も決め手となりました。 アーキテクチャとデプロイの仕組み Strapi は、FACTORY のECサイトと同様に AWS にてホスティングされており、ECS や Aurora を使ったシンプルなアーキテクチャで構成されています。CMS プラットフォームとしては、FACTORY の WEBアプリケーションからは独立させ、Strapi を使用するのは、主に事業部などの社内の部署で、記事の執筆や公開に限定することとしました。 記事を公開する際は、WEBアプリケーションのビルドを走らせ、その際に Strapi の API から記事情報を取得しページへ埋め込むようにしました。その結果、ユーザーのクライアントから直接 Strapi へはアクセスせず、CMS環境は閉じたネットワークとなることで、外部からの不要なアクセスを無くした形となります。 カスタマイズ事例 次に、導入を進めるにあたって、カスタマイズした部分をいくつか紹介していきます。 新規プラグインの作成 前述した FACTORY マガジンの中に お客様の声 という、実際にFACTORYで商品をご購入し施工頂いたお客様のインタビュー記事を載せているコンテンツがあります。 こちらの記事には、施工を行った車種の情報や商品を紐づける必要があり、デフォルトでの入力BOXでは「車種の名前(例:RAV4)」や「商品名(例:ヘッドランプデザインアップグレード)」を直接入力してもらう形となってしまいます。 車両情報入力 ただし、上記のようなフリーでの入力となると、名前を間違って登録してしまう可能性があったり、ブログのように同じ車種や商品での記事検索を行うには、FACTORY 内部で持っている商品IDなども記事に紐づいていたほうが、後々活用しやすいかと思い、EC サイトのフロントエンドで呼んでいる BFF から、これらの入力BOXのリストを作ることにしました。 車両選択 商品選択 このように、作成したカスタムプラグインにより、商品・車種情報をミス無く紐づけることができ、更に画像も出てくるようにカスタマイズしたので、執筆者が直感的に選択できるようになったかと思います。また、このように EC サイトで使っている BFF を使いまわせたりできるのも、セルフホスト型の利点かなとも思いました。(SaaSなどのサービスからだと、どうしてもセキュリティリスクや、上記のような柔軟なカスタマイズには少し不向きかと思われます) :::message カスタムAPIを作成した際の記事がすでに公開されておりますので、そちらもご一読頂けると幸いです。 StrapiにカスタムAPIを実装する ::: 既存プラグインのカスタマイズ 別のカスタマイズ事例としては、記事にタグを紐づけることにより、同じタグで同様の記事を検索するという要件を実現するため、こちらの tagsinput という既存のプラグインを導入しました。 ただ、こちらのプラグインは、入力したタグを [{ name: tag1 }, { name: tag2 }] のように連想配列として、データベースへ保存しており、タグから検索する API を作る際に、検索ロジックが複雑になっていました。 そこで、検索をよりシンプルにするため、プラグインを少しカスタマイズし、下記のように入力されたタグを単純に文字列の配列 [tag1, tag2] として保存する形に変更しました。 https://github.com/canopas/strapi-plugin-tagsinput/blob/1.0.6/admin/src/components/Input/index.js#L29-L36 @@ -26,8 +26,7 @@ const { formatMessage } = useIntl(); const [tags, setTags] = useState(() => { try { - const values = JSON.parse(value); - return values.map((value) => value.name); + return JSON.parse(value) || []; } catch (e) { return []; } https://github.com/canopas/strapi-plugin-tagsinput/blob/1.0.6/admin/src/components/Input/index.js#L64-L70 @@ -38,7 +37,7 @@ onChange({ target: { name, - value: JSON.stringify(tags.map((tag) => ({ name: tag }))), + value: JSON.stringify(tags), type: attribute.type, }, }); このように既存のプラグインを、少しだけ我々の使い方にあわせるように修正を加えるといった事も簡単に行うことができます。 その他にも様々なカスタマイズを行ってきましたが、その中でも Strapi の中で記事の投稿に使われているリッチエディターである CKEditor に、Video タグを埋め込めるようにしたカスタマイズについて、少し長くなりそうなので別記事にて紹介する予定です。 最後に KINTO のサービスの中で、まずは FACTORY が先陣を切って OSS の CMSツールである Strapi を導入しました。リリースした Strapi を使って記事の執筆・投稿している事業部からは、「SaaS のCMS サービスより使いやすくなった」などの意見を頂けていて、上々の滑り出しとなったかなと思っております。また「こうして欲しい」などの要望も出始めているので、OSS でのカスタマイズ性を武器にそれらに応えていければと思っております。 運用も開始したばかりですが、色々な経験値をためて、記事の執筆に留めることなく、Strapi を使った新しいソリューションを考えていくのも良さそうです。KINTO にて SaaS のCMSを使っている他のサービスについても、FACTORY での使い方を共有し社内での横展開を目指して行こうと考えております。
アバター
はじめに こんにちは、KINTO テクノロジーズ ( 以下、KTC ) SCoE グループの桑原 @Osaka Tech Lab です。 SCoE は、Security Center of Excellence の略語で、まだ馴染みのない言葉かもしれません。KTC では、この 4 月に CCoE チームを SCoE グループとして再編しました。SCoE グループについて知りたい方は、 クラウドセキュリティの進化をリードする SCoE グループ を参照ください。 また、KTC の関西拠点である Osaka Tech Lab については、 Osaka Tech Lab 紹介 をご参照ください。 本ブログでは、2024 年 7 月 4 日から 6 日に開催された『第 28 回サイバー犯罪に関する白浜シンポジウム』の参加レポートをお届けします。 まず最初に、"白浜" という場所をご存じない方へ。 白浜とは、和歌山県白浜市のことです。白浜は、美しい海、砂浜、そして温泉と、魅力あふれる観光地となっています。また、国内で最大の 4 頭のパンダを飼育しているアドベンチャーワールドも白浜にあります。 シンポジウム参加者の皆さんは、サイバーセキュリティの知見を深めるだけでなく、魅力あふれる白浜も堪能されたのではないでしょうか。 シンポジウム概要 テーマは、「激変する環境、複雑化するサイバー犯罪にどう立ち向かうのか?」です。 "サイバー犯罪" というイベント名ではありますが、最近発生している一般的なセキュリティ脅威や話題についての講演やパネルディスカッションも行われました。このシンポジウムでは、「サイバーセキュリティはひとつの組織で守れるものではない」という理念のもと、企業、官公庁、教育機関などの横のつながりを大事にしています。そのため、「その場限り」の話も多く、現地に行かなければ聞けない「生の声」を得ることができました。 昼の部は、 和歌山県立情報交流センターBig・U 、夜の部は、約 8 km移動して、 ホテルシーモア で開催されました。 (昼の会場が、実は白浜市ではなく隣の田辺市にあることについては、突っ込んではいけません) シンポジウムでは、多くの興味深い講演や発表がありましたが、ここでは私が印象に残ったキーワードを 2 つご紹介します。プログラムの一覧については 公式サイト をご確認ください。 キーワード 1 : 組織を越えた協力体制 このシンポジウムでは、ネットワーキングを非常に重要視しています。 シンポジウム実行委員長の挨拶や複数の講演者が、「脅威に対抗することは一企業・一組織単独では困難であり、点ではなく面で守ることが重要」と強調していました。 これは、サイバー攻撃の複雑化や多様化に対処するためには、異なる業界や産官学の壁を越えた協力が不可欠であることを示しています。 企業間の情報共有や官公庁・警察機関との連携、そして教育機関との協力が、より強固なセキュリティ対策を実現するための鍵となる共通認識を持つことが重要ということです。 警察関係者も多く参加しており、民間企業の方と意見交換をされていました。実際、このシンポジウムで私に最初に名刺交換の声掛けをいただいたのは某県警の方でした。 また、セキュリティインシデントは自社だけで対応することが困難であり、各組織の経験やノウハウを共有し、効果的なセキュリティ対策を実施することが重要であることも強調されていました。 夜の BOF(Birds Of a Feather)では、組織や業界を越えた同じ悩みを持つ参加者が集まり、活発な意見交換が行われました。 キーワード 2 : 生成 AI とセキュリティ トレンドである生成 AI のセキュリティについて取り上げた講演が複数ありました。その中でも特に印象的だったのが富士通研究所様の講演です。この講演では、生成 AI に関するセキュリティの最新動向と実践的な知識を提供してくれました。 富士通研究所様の講演で強調されていたのは、「 AI で守る」と「 AI を守る」の両面でセキュリティを考慮する必要があるということです。 AI で守る : サイバーセキュリティの防御手段としての AI セキュリティインシデントを予防するための AI 「 AI で守る」分野では、既存のセキュリティ適用範囲が「生成 AI を使って守れる」ことによって大きく拡大しています。富士通研究所様で実施している、セキュリティ AI コンポーネントを拡充し、DevSecOps をフレームワーク化するという取り組みをご紹介いただきました。 AI を守る : AI に潜む脅威、AI への攻撃 AI を攻撃から守る 「AI を守る」分野では、生成 AI がもたらすリスクについても詳しく説明されました。生成 AI に対するサイバー攻撃の具体的な手法や、それに対する対策アプローチについても言及されていました。AI への攻撃は「情報を盗む」と「 AI を騙す」について、具体例を交えてご紹介いただきました。 この講演では、生成 AI を活用したプロダクトを構築する際に考慮すべきセキュリティ観点が体系的にまとめられており、非常に参考になりました。例えば、生成 AI の開発プロセスにおけるセキュリティガイドラインの策定や、ガードレール・脆弱性スキャナのサンプルなど、具体的なガイドラインへの落とし込みに役立つインプットが得られました。 来年参加される方向けの TIPS 来年参加される方向けの TIPS もいくつかご紹介します。 チケット確保 : この白浜シンポジウムも含めて温泉シンポジウム系(道後、越後湯沢、熱海、九州)は、非常に人気が高く、プラチナチケットとなっています。発売開始日は必ず確認しておき、早めに確保することをお勧めします。また、昼食のランチ(弁当)チケットも合わせて購入をお勧めします。これは会場内・会場周辺で昼食を確保できる場所が限られているからです。 交通手段の確保 : 白浜駅から会場までシンポジウムが提供するシャトルバスがありますが、時間の融通は利きません。公共交通機関での会場移動は難しいため、シャトルバスの時間には十分気を付ける必要があります。レンタカーの利用も一つの手段です。(私は会社の許可を得て、自家用車で参加してましたので、本当に助かりました。) 宿泊先の選定 : 宿泊先は、シャトルバスの利用を考慮すると、夜の会場(ホテル)に近い場所を選ぶと便利です。夜の会場周辺は、温泉地ですので多くの宿泊施設があります。 ネットワーキング : 名刺を大量に持っていくことをお勧めします。ネットワーキング重視のシンポジウムですので、積極的に交流した方が得るものが多くなります。 まとめ サイバーセキュリティは、組織や業界を越えたつながりが重要です。現地でしか聞けない「生の声」は、本当に貴重なものがあります。 このような有益なシンポジウムを開催してくださった実行委員や講演者の皆様、スポンサー企業の皆様、そして参加者の皆様に感謝いたします。 皆さんも来年、白浜の美しい夕日を眺めながら、サイバーセキュリティにどっぷり浸かってみてはいかがでしょうか。 さいごに 私の所属する SCoE グループでは、一緒に働いてくれる仲間を募集しています。クラウドセキュリティの実務経験がある方も、経験はないけれど興味がある方も大歓迎です。お気軽にお問い合わせください。 詳しくは、 こちらをご確認ください。
アバター
Hello, I'm Chris, a frontend developer in the Global Development Division at KINTO Technologies. When developing frontend components, you’ve probably heard about using props to pass necessary information. Popular frameworks such as Angular, React, Vue, and Svelte each have their own ways of implementing this functionally for passing data between components. Discussing all of these would make this article very lengthy, so I will focus on Vue, which is commonly used in our Global Development Division. When considering component reusability, relying solely on props may not be sufficient. This is where slots come in. In this article, I will explain both props and slots, comparing their usage through practical examples. Passing information with props For example, let’s say you need to implement a reusable component for a table with a title. By passing props for the title, headers, and data, you can easily achieve this. #Component <template> <div> <h5>{{ title }}</h5> <table> <tr> <th v-for="header in headers" :key="header">{{ header }}</th> </tr> <tr v-for="(row, i) in data" :key="i"> <td v-for="(column, j) in row" :key="`${i}-${j}`">{{ column }}</td> </tr> </table> </div> </template> <script> export default { name: 'DataTable', props: { title, headers, Data }, } </script> #The parent that calls the component <template> <DataTable :title="title" :headers="headers" :data="data"/> </template> <script> // Omit the import statement for the component export default { data() { return { title: 'Title', headers: ['C1', 'C2', 'C3', 'C4'], data: [ { c1: `R1-C1`, c2: `R1-C2`, c3: `R1-C3`, c4: `R1-C4`, }, { c1: `R2-C1`, c2: `R2-C2`, c3: `R2-C3`, c4: `R2-C4`, }, ] } }, } </script> Using the code above, you can create the following table. (I've added some simple CSS styling, but that's not relevant to this article so I won't go into details here.) To elaborate a bit on the use of props, Vue.js allows for simple type checking and validation of the data received from the parent component, even without using TypeScript. The following is an example, but for more details, please check the Vue.js official documentation . (Adding such settings to all sample code in this article would make it too long, so it’s omitted.) <script> export default { props: { title: { // A prop with a String type. When a prop can have multiple types, you can specify them using an array, such as [String, Number], etc. type: String, // This prop must be provided by the parent component. required: true, // Prop validation check. Returns a Boolean to determine the result. validator(value) { return value.startsWith('Title') } }, }, } </script> Issues with using only Props While props are indeed convenient for specifying types and validating values, they can sometimes feel inadequate depending on what you want to achieve. For example, have you ever encountered requirements like these? Make the value displayed in table cell bold, italic, or change the text color based on conditions. Display one or more action buttons in each row of the table, which can be disabled based on conditions. These requirements make sense, but implementing them only using props can lead to complex code. To change the style of a cell, you might need to pass the logic for determining the style as a prop, or add markers to the data objects indicating which values need style changes. Similarly, to add buttons to each data row, you would need to pass the button information as props to the component. For example, if you extend the initial sample code, it would look like this: <template> <div> <h5>{{ title }}</h5> <table> <tr> <th v-for="header in headers" :key="header">{{ header }}</th> </tr> <tr v-for="(row, i) in data" :key="i"> <!-- The class information is retrieved using a function that evaluates the received styles --> <td v-for="(value, j) in row" :class="cellStyle(value)" :key="`${i}-${j}`" > {{ value }} </td> <!-- If there are buttons, prepare a separate column for them --> <td v-if="buttons.length > 0"> <button v-for="button in buttons" :class="button.class" :disabled="button.disabled(row)" @click="button.onClick(row)" :key="`${button}-${i}`" > {{ button.text }} </button> </td> </tr> </table> </div> </template> <script> export default { props: { title, headers, data, // To receive the cell style logic as a prop cellStyle, // To receive button information as a prop buttons, }, } </script> <template> <!-- Passing the function that returns class information and button-related information as props --> <DataTable :title="title" :headers="headers" :data="data" :cell-style="cellStyle()" :buttons="buttons" /> </template> <script> export default { data() { return { // Other data information is omitted. Buttons: [ { text: 'edit', class: 'btn-primary', disabled: (rowData) => { // Logic to determine whether to disable the button }, onClick: (rowData) => { // Logic after button press }, }, { text: 'delete', class: 'btn-danger', disabled: (rowData) => { // Logic to determine whether to disable the button }, onClick: (rowData) => { // Logic after button press }, }, ], } }, methods: { cellStyle() { return (val) => { // The logic that returns the necessary style class information } } } } </script> Here is a screenshot showing the result of applying styles to cell text based on conditions and disabling buttons as needed. However, if you want to further control the HTML structure within the cell (e.g., adding <p> , <span> , or <li> tags, etc.), you will need to pass the HTML code as a string prop to the child component and use v-html to render it. While v-html is a convenient method to render HTML, it can make the code harder to read when you include a lot of dynamic elements because you are constructing the HTML code as a string. In summary, when using only props, you need to carefully consider how the child component will receive the data . Using slots to complement the limitations of props This is where the slot feature comes in. As explained in the Official Documentation , you can create a slot in a component and pass HTML information from the calling template into that slot, enabling you to implement the desired content within the specified slot area. The illustration above is a conceptual image. The box on the left represents a component using props, while the box on the right represents a component using slots. In the case of props, each entry point is narrow and the type is fixed, meaning that the developer can only pass information defined by the component. In contrast, slots provide a much wider entry point, giving the developer more control over what is passed to the component. For example, let's try implementing a data table using slots as described above. <template> <div> <!-- default slot --> <slot /> <!-- table slot --> <slot name="table" /> </div> </template> <template> <DataTable> <!-- When you write HTML code inside the component, it is automatically placed into the slot declared on the component side --> <!-- If not wrapped in a template, it is placed into the default slot --> <h5>Title</h5> <!-- Placed into the slot named table --> <template #table> <table> <tr> <th v-for="header in headers" :keys="header">{{ header }}</th> </tr> <tr v-for="(row, i) in data" :keys="`row-${i}`"> <td v-for="(column, j) in row" :class="{ 'font-italic': italicFont(column), 'font-weight-bold': boldFont(column) }" :key="`row-${i}-col-${j}`" > {{ column }} </td> <td> <button :disabled="editDisabled(column.c1)" @click="edit(column.c1)">Edit</button> <button :disabled="destroyDisabled(column.c1)" @click="click(column.c1)">Delete</button> </td> </tr> </table> </template> </DataTable> </template> <script> export default { // Omit data information methods: { edit(id) { // Logic to edit the data for that row }, destroy(id) { // Logic to delete the data for that row }, italicFont(val) { // Logic to determine whether the font should be Italic }, boldFont(val) { // Logic to determine whether the font should be bold }, editDisabled(id) { // Logic to determine whether the edit button should be disabled }, destroyDisabled(id) { // Logic to determine whether the delete button should be disabled } }, } </script> In this example, no props are passed to the component, making the code look quite clean. However, there's one issue: it allows developers to implement anything they want. For instance, when using the component mentioned above, it is expected that developers use the specified tags (such as for the title and for the table, etc.) and apply the appropriate styles. Yet, due to a lack of communication or insufficient implementation knowledge, developers might use different tags. This can lead to differences in appearance, and you'll need to double-check during testing to make sure it fits on each screen size. <template> <DataTable> <!-- Use h1 instead of h5 --> <h1>Title</h1> <template #table> <!-- Use <div> instead of <table>, <tr>, and <th> --> <div> <div> <div v-for="header in headers" :keys="header">{{ header }}</div> <div></div> </div> <div v-for="(row, i) in data" :keys="`row-${i}`"> <div v-for="(column, j) in row" :key="`row-${i}-col-${j}`"> {{ column }} </div> <div> <button :disabled="editDisabled(column.c1)" @click="edit(column.c1)">Edit</button> <button :disabled="destroyDisabled(column.c1)" @click="click(column.c1)">Delete</button> </div> </div> </div> </template> </DataTable> </template> Using <div> tags for everything in a table may seem like an extreme example, but it‘s safer not to give more freedom than necessary. While interpretations may vary by company or team, my ideal approach is to consult with designers and allow flexibility only where necessary. Before deciding whether to use props or slots, it’s important to determine which parts of the implementation can be flexible and which must follow the specific guidelines. <template> <div> <!-- Use props to ensure the text is always placed in <h5> --> <h5>{{ title }}</h5> <!-- Ensure the use of <table> tag --> <table> <tr> <!—Use props to pass header information to enforce the use of <th> --> <th v-for="header in headers" :key="header">{{ header }}</th> </tr> <!-- Dynamically generate slots based on the number of rows in the passed data --> <!-- Use v-bind to pass data to the parent template --> <slot name="table-item" v-for="row in data" v-bind="row" /> </table> </div> </template> <script> export default { data() { return { title, headers, data } } } </script> <template> <!—Pass the title and headers as props --> <DataTable title="Title" :headers="headers" :data="data"> <!-- Receive the v-bind data on the component side --> <template #table-item="row"> <!—Accept a row of data and define how it should be displayed --> <tr> <td v-for="(column, i) in row" :key="`col-${i}`"> {{ column }} </td> <td> <button :disabled="editDisabled(column.c1)" @click="edit(column.c1)">Edit</button> <button :disabled="destroyDisabled(column.c1)" @click="click(column.c1)">Delete</button> </td> </tr> </template> </DataTable> </template> By the way, when using slots, you can utilize this.$scopedSlots on the component side to check which slots are being used by the parent and how they are being utilized. There are various use cases; for instance, you can determine what tags are being used within a slot. This provides mild form of validation against the issue of excessive flexibility with slots that was mentioned earlier. <template> <DataTable title="Title" :headers="headers" :data="data"> <template #table-item="row"> <!-- Notify the developer in some way if it is not <tr> --> <div> <td v-for="(column, i) in row" :key="`col-${i}`"> {{ column }} </td> </div> </template> </DataTable> </template> Summary To sum up, although using props is the easiest way to develop reusable components with Vue, it lacks flexibility, as shown in the examples in this article. On the other hand, using slots gives developers more freedom in their implementation, but for various reasons, this freedom can lead to unexpected methods being used, making it difficult to ensure quality. Therefore, it is important to involve stakeholders in the component’s development to decide in advance how much flexibility each part of the component should have. Based on these decisions, you can use props and slots appropriately to balance control and flexibility. Furthermore, providing thorough documentation will help ensure that users of the component understand the specifications and the intended usage.
アバター
KINTOテクノロジーズで my route(iOS) を開発しているRyommです。 先日7/5に 室町情報共有会 LT大会 🎋七夕スペシャル🎋 を開催しました! 今回はこちらで発表した「Slackを使いこなせ!Slack効率3000倍」というLTの文字起こしバージョンです。 資料はこちら @ speakerdeck Motivation みなさん、Slackは使いこなせていますか? Slackには様々な機能があります。それらを使いこなせるようになると、業務のちょっとした困りごとなどを改善できるようになるかもしれません! ここではSlackの基本的な機能を超特急で紹介します。 この機能はあれに使えそう!や、これとこれを組み合わせたら面白そう!など考えながら読んでみてください。 Mastering "Search" まずは、検索機能です! Slackの検索では、クエリを駆使することで様々な使い方をすることができます。 Googleなどでも定番のフレーズ検索やマイナス検索はもちろん、日付の範囲を指定したり、チャンネルを指定したり、それからリアクションスタンプから検索したり、メッセージなどの共有元を指定して検索したりできます。 もちろん、クエリを覚えなくてもGUIでフィルター検索することもできますが、いつも見るものであればクエリをメモしておいて検索バーに貼り付けるだけで検索できるため、速度向上につながります。 https://slack.com/intl/ja-jp/help/articles/202528808-Slack-内で検索する Mastering “Custom responses” 続いて、カスタムレスポンスです! slackbotに任意のレスポンスを設定することができます。 Slackbotのカスタマイズページを開くと設定することができます。 左の部分に設定したいずれかのフレーズを呼び出すと、その隣のいずれかの回答をランダムで返します。その性質を利用して、右のようにサイコロをランダムに回したりすることができます。 https://slack.com/resources/using-slack/a-guide-to-slackbot-custom-responses Mastering “Mentions” メッセージを広く周知したい時にはチャンネルメンションやhereメンションを使います。 ワークスペース全体にメンションできる @everyone もありますが、まず使う機会はないです。 これらのメンションは通知を切っている場合や、スレッドの中では使うことができません。 また、基本的に全体メンションは通知が汚染されやすくなるため、時と場合を考えて使いましょう。 https://slack.com/help/articles/202009646-Notify-a-channel-or-workspace Mastering “User groups” そこで、ユーザーグループを活用します。 ユーザグループを作成すると複数人をひとまとめにメンションできたり、グループごとチャンネルに追加したりできます。 さらに、グループに対して紐づくチャンネルを登録できるため、オンボーディングなどで複数のチャンネルに追加したい時も、ユーザグループに追加するだけでOKになります。 https://slack.com/help/articles/212906697-Create-a-user-group Mastering “Stock-type information” 情報は、ストック型とフロー型の2つに大きく分類することができます。 ストック型情報は蓄積される情報、フロー型情報は流れる情報です。 Slackにおける通常のやり取りはフロー型情報にあたり、流されては困るストック型情報はConfluenceなどにまとめていることが多いでしょう。 Slack内でもフロー型情報をストック型として置いておく方法がいくつかあります。 あとで見る、ピン留め、キャンバス、ブックマーク、そしてリストです。 ワークフローや検索との相性もいいので、色々組み合わせて連携しやすいです。 Mastering “Notification” 通知設定もかなり自由に設定することができます チャンネルをセクションで分けて、それぞれに対してミュートや表示設定をしたり、キーワードを設定して特定のフレーズが呼ばれた時に通知したり、Reacji Channelerというアプリを使うと特定の絵文字をリアクションされた時に特定のチャンネルに転送して通知させたりすることもできます。 Mastering “Reminder” 指定のチャンネルにリマインダーを送ることができます。 繰り返し設定することも可能です。 ワークフローやメッセージのあとで送信機能との使い分けを工夫してみると良いでしょう! https://slack.com/intl/ja-jp/help/articles/208423427-リマインダーを設定する Mastering “Huddle” Slack上で通話ができるハドル! ハドルのいいところはなんといってもチャットなどをそのままSlack上に残せるところです! そしてURLを知らなくても通話に参加できるので、飛び入り参加をしやすいところも利点です。 ハドルへ参加するURLを作成することもできるので、Outlookの予定表から飛ぶようにすることもできます。 一人でもくもくしている時にも、ハドルのゴキゲンミュージックを楽しむことができます。 Mastering “Email forwarding” Slackチャンネルにメールを送ることもできます。 メーラー側でチームに共有したいメールをフィルターして、発行したチャンネル用メールアドレスに転送するなどして使うことができます。 Mastering “Workflow” Slackにおける自動化で欠かせないのがワークフロー! 問い合わせのテンプレートを統一させたい時や、集めたデータをスプシに集約させたい時、それからアクションを組み合わせたい時、チャンネルに加入した人へのオンボーディングなど、多くの場合に活用できます。 スプレッドシートやGASなどを組み合わせることもでき、さらに可能性は広がります。 https://slack.com/intl/ja-jp/help/articles/17542172840595-ワークフローを作成する---Slack-でワークフローを作成する Mastering “Custom App” ワークフローでは実現が難しいことをしたい場合、SlackAPIを使用してカスタムアプリを作ることもできます。 最近ではSlack CLIというものが登場し、より簡単にカスタムアプリの構築ができるようになりました。 コードを書く必要はありますが、自由度がグッと上がり、様々なことがSlack上で実行可能になります。 https://api.slack.com/docs おわりに 意外と知らない人多いんだなーと思ってLTで話してみたのですが、ブログにしてほしい!という声を割といただいたので、ブログにも起こしてみました。 どのくらい使いこなせていましたか?知らなかった機能があれば、ぜひ今日から活用してみてください!
アバター
🎉 iOSDC Japan 2024のゴールドスポンサーとして協賛します こんにちは!もうすぐお盆シーズンですね。今年は子供と一緒に遊びに行こうと思っています。ヒロヤ (@___TRAsh) です。さて、今回は僕たちiOSチームからお知らせです。 KINTOテクノロジーズはiOSDC Japan 2024のゴールドスポンサーとして協賛します🙌 今回、初出展となるiOSDC Japan 2024ではコーディングクイズを開催します。オリジナルノベルティも配布する予定なので、ぜひ、ブースにお越しください! ということで、せっかくの機会なので、今回はiOSメンバーにインタビューをしました。どういったメンバーが居るのか参考になれば幸いです。 🎤 インタビュー 今回はKINTO Unlimited iOSチームにインタビューをしました。 KINTO Unlimitedは海外メンバーも多く、多国籍なチームです。日々の業務も基本英語(ほとんどのメンバーが日本語も話せる)で行われています。 T.O.さん ──簡単に自己紹介をお願いします こんにちは、T.O.です。KINTOテクノロジーズでiOSエンジニアをしています。前職までWebのフロントエンドをやっていて、モバイル開発はここにきて初めてで、GitHubも初めて業務で使いました。入社して2年半くらいですが、いろいろなプロジェクトに携わって色々なアーキテクチャやモダンな開発手法を学びました。 ──この会社に来て変わったことは何ですか? iOSエンジニアとしてのスキルが身についたことです。あと、KINTOテクノロジーズに入社すると共に上京してきたので、生活環境も変わりました。当時はコロナ禍だったので、人が少なく、初めて上野公園でパンダを見れたのは印象的でした。 ──この会社のいいところは何ですか? すごく話しかけやすい雰囲気があります。プライベートなことでも技術的なことでも気軽に相談できますね。出社でもリモートでも気楽に話せるのはありがたいです。あと、子育て世代にはありがたい福利厚生が充実しているのもいいですね。 ──今後チャレンジしていきたいことは何ですか? 今後はARやMLを使った開発をもっとしていきたいですね。ちょうどそういった分野に関わるプロジェクトにいるので、もっと極めていきたいです。あと、子供とゲームして遊びたいですね。 ──最後に一言お願いします! すごく幅のきく働き方ができて、とても働きやすいです👍 V.V.さん ──簡単に自己紹介をお願いします ロシア出身のロシア人です、iOSを8年ぐらいやっています。趣味はTRPGと子育てです。今まではロシアでWindowsのデスクトップアプリを作ったり、アメリカで救急車のサービスを作ったりしていました。日本に来て数年別の会社で働いて、KINTOテクノロジーズに入社しました。 ──この会社に来て変わったことは何ですか? 今まで小さな会社で働いていて、子供もいるので不安がありましたが、KINTOテクノロジーズはトヨタのグループ会社なので安心して働けています。給料ベースはそんなに変わらないけど、手当が多いので、かなり良くなったと思う。ボーナスでダブルベッドを買えました。 ──この会社のいいところは何ですか? 今まで、小さなチームで働いていたので、技術的な相談やメンタリングできる人がいなかったのですが、KINTOテクノロジーズは技術的な相談がしやすいです。また、自分の知識を共有できる機会もあれば、経験豊富なメンバーも多いので、刺激が多いです。 ──今後チャレンジしていきたいことは何ですか? ARやMLの機能開発をすることがあってもっと興味が出てきたので深ぼっていきたいです。また、家族をしっかり養っていきたいです。 ──最後に一言お願いします! 成長する機会があるから来てね✋ S.C.さん ──簡単に自己紹介をお願いします 韓国で生まれて、カナダに移住して今は日本にいるカナダ人です。日本の映画や文化に興味があって、友人も多くいたので日本に来ました。日本に来て10年ぐらい経ちます。前職ではバックエンドもやっていました。現在はKINTO Unlimited iOSチームのチームリードをしています。 ──この会社に来て変わったことは何ですか? プロジェクト問わずモバイルエンジニアがあつまったグループなので、iOS開発にフォーカスしたスキルアップがしやすいです。また、日本語で業務する時間も増えたのは嬉しいです。 ──この会社のいいところは何ですか? 前職ではシステムが巨大でほとんどメンテナンスに近い仕事をしていましたが、KINTOテクノロジーズでは新機能の開発が多く、新しい技術を学ぶ機会が多いです。モダンな技術を取り入れやすいのもいいですね。あと、勉強会を積極的に行なっていて、ナレッジシェアの機会が多い事も良いです。 ──今後チャレンジしていきたいことは何ですか? チームリードになったばかりなのでリーダーシップを伸ばしていきたいですね。それと、OSによる実装差異が気になるので、Androidの知識も広げていきたいです。あと、大学院に行っているんですが、卒業したいです。 ──最後に一言お願いします! 今まで出来てなかったモダンな開発環境で色々な経験ができて楽しいです👍 🚙 まとめ 弊社はまだまだ成長中の会社ですが、始まって間もないプロダクトも多いので、モダンな手法を取り入れやすい開発環境になっています。 多様性のあるチームで働くことができるので、新しい文化や技術を学ぶ機会が多いです。そんな環境で働いてみたい方は、ぜひ弊社にご応募ください! https://www.kinto-technologies.com/recruit/ ということで、 :::message チャレンジトークンはこちらになります! #KTCでAfterPartyやります ::: 9/9(月)にTimeTree様、WealthNavi様と3社合同で初のコラボイベントiOSDC JAPAN 2024 AFTER PARTYを開催します🥳 場所は日本橋室町にある弊社オフィスになります🗺️ こちらにもぜひご参加ください! https://kinto-technologies.connpass.com/event/327743/ 当日は猛暑になることが予想されます。水分補給をしっかりして、楽しいイベントをお過ごしください! ブースで皆さまのご来場をお待ちしております✋
アバター
Introduction At KINTO Technologies' Platform Engineering team, we were not fully satisfied with our current logging solution. With new AWS services available, we saw an opportunity to enhance our logging platform, making it both easier to use and more cost-effective - a win-win situation! Of course we could not just tear down everything already in place to replace it with the new shiny services - that would be like replacing the engine of a car while it's still running! We needed to investigate what new services we could use and how to configure them to meet our needs. As part of our exploration of using OpenSearch Serverless for our new log platform we needed to find a solution for our alert system. Currently, we are using the Alerting feature of our OpenSearch cluster, but this feature is unavailable in the serverless instances. Thankfully, as of AWS Managed Grafana version 9.4, the Grafana OpenSearch plugin could use an OpenSearch Serverless instance as a data source (see the Grafana Opensearch plugin page ), so we could use Grafana for our alerting needs! We still needed to figure out how to configure both services so that they could work nicely together. At the current state of our investigation we had already created an OpenSearch Serverless instance and tested log ingestion from all of the source we wanted to use. The remaining task was to set up a test Grafana instance in our Sandbox to use our serverless instance as a data source. At the time of writing this article, the AWS documentation is not explicit on how to do exactly that. As engineers, we often don't have a step-by-step guide for every task. This is when we need to explore and experiment with whatever we are building to see what works. We also asked for help from the AWS Support to narrow down all the necessary permissions, where they had to escalate our request for help to both the Amazon Managed Grafana internal team, and to the OpenSearch team as the documentation does not exist yet. This motivated us to write this article to share the knowledge. A quick self-introduction before continuing: I'm Martin, a Platform Engineer at KINTO Technologies. I joined the team last year and started working with AWS sporadically since then. Working on this project has been a great learning experience for me and I'm excited to share it with you! The biggest takeaway I got from this project is that the AWS Support is a great resource and you should not hesitate to ask for help when you need it. Setting up our environment In this article, we'll set up everything using the AWS Console. You can, of course, use your favorite Infrastructure as Code tools with AWS to build the same configuration. This article assumes you are familiar with the AWS Console and already have an Opensearch Serverless instance running. Please note, the configurations used in this article prioritize simplicity. I strongly recommend reviewing and adjusting these settings to align with your organization's security requirements. Setting up the IAM role Before anything else, we will need to create an IAM role for our Grafana instance to use. If you plan to use other AWS services with your Grafana workspace, it might be better to select the Service managed option when creating the Grafana workspace. You can then update that role created by AWS or provide the ARN of your custom role when setting up the data source in Grafana. Here is the trust policy needed when creating the IAM role: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "grafana.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } You can get the same trust policy by selecting the AWS service Trusted entity type and select AmazonGrafana in the Use case section. Here is the permission policy required for accessing OpenSearch Serverless from Grafana, with special thanks to the AWS Support team for escalating our request to the Grafana and OpenSearch teams to provide us with the minimum necessary permissions.: { "Statement": [ { "Action": [ "es:ESHttpGet", "es:DescribeElasticsearchDomains", "es:ListDomainNames" ], "Effect": "Allow", "Resource": "*" }, { "Action": "es:ESHttpPost", "Effect": "Allow", "Resource": [ "arn:aws:es:*:*:domain/*/_msearch*", "arn:aws:es:*:*:domain/*/_opendistro/_ppl" ] }, { "Action": [ "aoss:ListCollections", "aoss:BatchGetCollection", "aoss:APIAccessAll" ], "Effect": "Allow", "Resource": [ "arn:aws:aoss:<YOUR_REGION>:<YOUR_ACCOUNT>:collection/*" ] } ], "Version": "2012-10-17" } OpenSearch Access Policy On the OpenSearch side, we need to add a Data access policy for our newly created IAM role. Even if we gave our IAM role the necessary permissions to access OpenSearch, we still need to create a Data access policy to allow the IAM role to access the data in the collections. See the AWS documentation for more information. In the serverless section of the OpenSearch Service page menu, select Data access policies, then click on the Create access policy button. Add a name and a description to your access policy, then select JSON as the policy definition method. Use the following policy, courtesy of the Grafana Opensearch Plugin documentation: [ { Rules = [ { ResourceType = "index", Resource = [ "index/<NAME_OF_YOUR_OPENSEARCH_INSTANCE>/*" ], Permission = [ "aoss:DescribeIndex", "aoss:ReadDocument" ] }, { ResourceType = "collection", Resource = [ "collection/<NAME_OF_YOUR_OPENSEARCH_INSTANCE>" ], Permission = [ "aoss:DescribeCollectionItems" ] } ], Principal = [ <GRAFANA_IAM_ARN> ] Description = "Read permissions for Grafana" } ] Update it with the name of your OpenSearch Serverless deployment and the ARN of the IAM role we created earlier. A little bit of Networking Before continuing with the creation of our Grafana instance, we are going to create a few networking resources. First let's create two Subnet in the same VPC as your OpenSearch Serverless deployment. Each subnet should be in a different Availability Zones. Once created, we need to update the Route Table of each subnet to add a new route from 0.0.0.0/0 to an Internet Gateway. Next, create a Security Group accepting Inbound HTTPS traffic from your VPC, and accepting all Outbound traffic on 0.0.0.0/0. With all of this in place, we can now create our Grafana instance! Creating your Grafana Instance Search for the Amazon Managed Grafana service in the Console search bar. On the service's homepage, use the button that the AWS engineer conveniently placed there to create the Grafana workspace. For the first step of the creation page, set the name and description of your Grafana workspace. Set the version to at least 9.4. Version 10.4 is the latest version available so I will be using that. On the next page, for Authentication access, select your preferred authentication method. I'll select AWS IAM Identity Center. In the Permission type section, select Customer managed and select the ARN of the IAM role you created earlier. I had this weird issue where after creating the Grafana workspace, it was using another IAM role than the role I selected so I had to update the workspace to use the correct role. It could be a bug or a misconfiguration on my side. For the sake of this article, we will agree that I definitely selected the correct role and that this was a bug. Ok? Great! In the Outbound VPC connection section, select the same VPC as the one in which your OpenSearch Serverless instance is deployed. For the Mapping and Security Groups, select the subnets and the security group we created earlier. In the Workspace configuration options section, make sure to select Turn plugin management on. For this tutorial, we will section Open Access in the Network access control section. Click on the next button and review your settings. Once the workspace is created, set up your authentification method. I selected AWS IAM Identity Center so I'll simply add my user and make myself admin. You should now be able to connect! Grafana Meets OpenSearch Serverless Before adding our OpenSearch Serverless data source, we need to install the OpenSearch plugin in our Grafana workspace. To do this, follow these steps: In the menu on the left, select Administration, then Plugins and Data, and finally Plugins. On the Plugins page, select All instead of Installed in the field at the top of the page. Search for the OpenSearch plugin and install it. Once installed, you should see an Add new data source button at the top right of the OpenSearch plugin page. Click on it. Next, configure the data source information to connect to your OpenSearch Serverless instance: HTTP Section: Add the URL of your OpenSearch Serverless instance in the URL field. Auth Section: Toggle on SigV4 auth and select the region where your OpenSearch Serverless instance is located. OpenSearch Details Section: Toggle on Serverless and set the index you want to use. Logs Section: Set the name of your message field and level field. Finally, click on Save & test. You should receive a message confirming that you have successfully connected to OpenSearch. You can now use this data source to create alerts and dashboards! Conclusion I hope this article has been helpful and that you can now set up your own Grafana instance with OpenSearch Serverless as a data source. For us at KINTO Technologies, using Grafana for alerting looks like a great choice for our new logging solution. With this setup, we'd have a robust, efficient, and cost-effective logging and alerting solution that meets our specifications. Personally, I find creating alert queries in Grafana to be more straightforward and flexible compared to OpenSearch. By the way, the Platform Group at KINTO Technologies is hiring! We are always looking for talented engineers to join our team. If you're interested in joining our team or want to learn more about what we do and what it's like to work here, please feel free to reach out to us! We have a nice web page with all our job listings here .
アバター
KINTOテクノロジーズで my route(iOS) を開発しているRyommです。 TextKitを使いたい場合など、未だUITextViewは必要になることが多いと思います。 UITextViewをSwiftUIで使えるようにUIViewRepresentableしようとしたところ、高さの調整にハマったので、その解決記事です。 結論 こんな感じでできます。 import UIKit struct TextView: UIViewRepresentable { var text: NSAttributedString func makeCoordinator() -> Coordinator { Coordinator(self) } func makeUIView(context: Context) -> UITextView { let view = UITextView() view.delegate = context.coordinator view.isScrollEnabled = false view.isEditable = false view.isUserInteractionEnabled = false view.isSelectable = false view.backgroundColor = .clear view.textContainer.lineFragmentPadding = 0 view.textContainerInset = .zero return view } func updateUIView(_ uiView: UITextView, context: Context) { uiView.attributedText = text } func sizeThatFits(_ proposal: ProposedViewSize, uiView: UITextView, context: Context) -> CGSize? { guard let width = proposal.width else { return nil } let dimensions = text.boundingRect( with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil) return .init(width: width, height: ceil(dimensions.height)) } } extension TextView { final class Coordinator: NSObject, UITextViewDelegate { private var textView: TextView init(_ textView: TextView) { self.textView = textView super.init() } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { return true } func textViewDidChange(_ textView: UITextView) { self.textView.text = textView.attributedText } } } わかりやすさのために背景色をつけてます 解説 makeUIView() において、 view.isScrollEnabled を  false にすると、改行がされなくなってしまう問題がありました。 setContentHuggingPriority() や setContentCompressionResistancePriority() を使うと、スクロール無効時も改行はされるようになりましたが、垂直方向の表示領域がうまく調整されません。2行以上のテキストを表示する場合、垂直方向の領域を超えた部分は消えてしまっていました。 func makeUIView(context: Context) -> UITextView { let view = UITextView() view.delegate = context.coordinator view.isScrollEnabled = false view.isEditable = false view.isUserInteractionEnabled = false view.isSelectable = true view.backgroundColor = .clear // こんな感じ? view.setContentHuggingPriority(.defaultHigh, for: .vertical) view.setContentHuggingPriority(.defaultHigh, for: .horizontal) view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) view.setContentCompressionResistancePriority(.required, for: .vertical) view.textContainer.lineFragmentPadding = 0 view.textContainerInset = .zero return view } (・〜・) そこで sizeThatFits() を使います。 これはiOS16から提供された、UIViewRepresentableでオーバーライドできるメソッドです。このメソッドを使うと、提案された親のサイズを使ってViewのサイズを指定することができます。 今回はViewに渡すテキストを NSAttributedString にしたかったため、受け取ったテキストの高さを計算します。 高さの計算方法は こちらの記事 を参考にしました。 func sizeThatFits(_ proposal: ProposedViewSize, uiView: UITextView, context: Context) -> CGSize? { guard let width = proposal.width else { return nil } let dimensions = text.boundingRect( with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil) return .init(width: width, height: ceil(dimensions.height)) } これだけだとViewの領域が sizeThatFits() で計算した大きさより大きくなってしまうため、 makeUIView() に以下の2つの設定を入れて余白を消します。 textView.textContainer.lineFragmentPadding = 0 textView.textContainerInset = .zero できました◎ おわりに sizeThatFits() でいい感じに計算すればいいんだ〜というところに辿り着くまでに結構遠回りしたので、記事にしてみました🤓
アバター
​ Hello ( º∀º )/ ​ This is Murayama from the Tech Blog Team and the Budget Control Group! In this article, I’d like to share insights from the organizing staff as we host our company's first external event, the "KTC Meet Up!" You can read another article about this event here, detailing everything from the study session planning to appointing supporting staff, written by the organizer, Kinchan ✍️ The first KINTO Technologies MeetUp! From start to launch ![](/assets/blog/authors/uka/ktc-meet-up/kinchan.png =500x) The Tech Blog team members are actively involved in supporting events! We don’t just manage the Tech Blog; we engage with everyone and help energize the company. That’s the kind of team we are! ('-')ง ​ Looking back to late June 2023, I joined the team because they were planning to host an external event in August. All team members have dual roles and belong to different groups. In my regular duties, I handle financial matters, so I took care of purchasing the necessary supplies and providing support on the event day. Since this was KINTO Technologies' first external event, we made sure to gather all the necessary equipment. ​ Although this event was held offline, we also purchased filming equipment! This means we can host online or hybrid events in the future as well 😳 It's great that our company is proactive in taking on new challenges!! I’ll share some photos from here: ​ Couldn’t wait until the test, so they started using it as soon as it arrived Everyone trying and experimenting together ​ The event day came in a flash! ​ Test Test! ​ This time, we decided to test streaming internally ​ Whoa! ​ We also made T-shirts! Matching! ​ Excited! ​ Let's get started! ​ ​ Celebrating the birthday of our first external event with birthday glasses ​ ​ Our manager! ​ ​ Individuality shining! ​ ​ The Tech Blog team also took the stage! ​ Good job presenting! ​ ​ Group discussions: each table was lively! ​ So busy talking, no time to eat… Said one of our speakers ​ Buzzing ​ ​ Looks fun! ​ ​ Note to self: beer and highballs were popular choices ​ Peace ​ Thumbs up ​ ​ ​ ​ And just like that, the two hours flew by! Here are the “standout moments” from the event, as highlighted by the support staff and organizing team: ​ When the collaborators gathered. The moment when the cleanup was finished. Everyone contributed voluntarily! Hearing "That was awesome!" and "When’s the next one?" as people left. Right after recruiting support staff for the event, everyone started sharing various ideas. The impact of arriving at the event venue on the day The discussions on the day. Everyone was so engaged in conversation! Receiving consultations. I enjoy being involved in such exciting interactions! It ended in a great success! We all worked together explaining how the camera works for example, and being able to accomplish this as One Team. Twitter was buzzing with excitement even before the event started. There was a strong sense of unity! Having many participants ask a variety of questions, and the post-event survey results included comments like, “We'll try this in our team too.” Seeing the organizers also enjoy themselves was fantastic! Having all the equipment perfectly set up on site. When most of the name tags were given out at reception! Colleagues from different specialties and departments working together to make the event a success. The event ran smoothly on the day, and it was wonderful to see everyone thinking about what they could do on their own initiative. ​ The review session was also very fruitful with many opinions exchanged. Everyone's cooperation was visible and made it a wonderful event! Conclusion ​ Stay tuned for an upcoming article from the support staff's perspective and another one featuring case studies presented by the speakers 😗 In the meantime, I'm sharing this article because I captured a lot of great photos and wanted to share them with you 😳 ​ Thank you for reading until the end!
アバター
こんにちは、KINTO テクノロジーズ (以下 KTC) DBRE のあわっち( @_awache ) です。 今回は AWS の提供するシークレットローテーションの機能を利用して、主に Aurora MySQL に登録されている データベースユーザーのパスワードを安全にローテーションする仕組みを導入したのでその導入方法やつまずいた点、さらに周辺で開発したものを全てまとめて紹介させていただきます。 かなり長いブログなので先に簡単に要約を記載させていただきます。 全体まとめ 背景 KTC ではデータベースユーザーのパスワードを一定期間でローテーションすることが義務付けられることとなった ソリューション 検討 MySQL Dual Password:MySQL 8.0.14以降で利用可能なDual Password機能を使い、プライマリとセカンダリのパスワードを設定 AWS Secrets Managerのローテーション機能:Secrets Managerを使い、パスワードの自動更新とセキュリティの強化を実現 採用 設定の容易さと網羅性のために、AWS Secrets Managerのローテーション機能を採用 プロジェクトの開始 プロジェクトの開始にあたり、インセプションデッキを作成し、コスト、セキュリティ、リソースに対する責任分解点を明確化した プロジェクト内で開発したもの Lambda 関数 AWS の提供するシークレットローテーションの仕組みを単純に使うだけでは KTC の要件に合わない部分が多くあったため、運用面を検討した結果、多くの Lambda 関数を作成する必要があった シングルユーザー戦略用 Lambda 関数 目的:単一のユーザーに対してパスワードをローテーション 設定:Secrets Manager に設定される。シークレットのローテーションを指定した時間に実行し、パスワードを更新する 交代ユーザー戦略用 Lambda 関数 目的:2つのユーザーを交互に更新し、高可用性を確保 設定:Secrets Manager に設定される。ローテーションの初回で2つ目のユーザー(クローン)を作成し、以降のローテーションでパスワードを切り替える シークレットローテーション結果通知用 Lambda 関数 目的:シークレットローテーションの結果を通知 トリガー:CloudTrail イベント(RotationStarted、RotationSucceeded、RotationFailed) 機能:DynamoDB にローテーション結果を保存し、Slack に通知。通知時に Slack のタイムスタンプを使用してスレッドに追記 ローテーション結果格納用 DynamoDB 管理 Lambda 関数 目的:ローテーションの結果を DynamoDB に格納し、エビデンスとしてセキュリティチームに提出 機能:CloudTrailのイベントをトリガーに Lambda を実行し、ローテーション結果を DynamoDB に保存。保存したデータを基に SLI 通知を行う SLI 通知用 Lambda 関数 目的:ローテーション状況を監視し、SLI 通知を行う 機能:DynamoDB から情報を取得し、シークレットローテーションの進行状況を監視。必要に応じて Slack に通知 ローテーションスケジュール決定のための Lambda 関数 目的:各 DBClusterID に対してローテーションの実行時間を決定 機能:既存のシークレットローテーション設定情報を基に、新しいスケジュールを生成し DynamoDB に保存。ローテーションウィンドウとウィンドウ期間を設定 ローテーション設定適用のための Lambda 関数 目的:決定したスケジュールを Secrets Manager に適用 機能:DynamoDB から取得した情報を基に、指定の時間でシークレットローテーションを設定 シークレットローテーション登録ツール 実際の登録にはローカルから実行できるツールを別途開発した Secrets Rotation スケジュール設定ツール 目的:データベースユーザーごとにシークレットローテーションのスケジュールを設定 機能:DynamoDB に保存された情報を基に、指定された DBClusterID と DBUser の組み合わせに対して、シークレットローテーションの設定を適用 最終的な全体アーキテクチャ もっとシンプルにできるかと思ったが想像以上に複雑に。。 ![全体像](/assets/blog/authors/_awache/20240812/secrets_rotation_overview.png =750x) 結果 シークレットローテーションの全プロセスを自動化し、セキュリティと管理の手間を削減 全体のアーキテクチャを構築し、ガバナンス制約を満たすシステムを実現 KTCは、シークレットローテーションを利用して、安全で効率的なデータベース運用を目指し、さらなる改善を続けていく 改めまして、ここから本編に入りたいと思います。 Introduction KTC ではデータベースユーザーのパスワードを 一定の短い期間でローテーション することが義務付けられることとなりました。ただパスワードのローテーションと言っても簡単に行えるものではありません。 データベースユーザーのパスワードを変更するためにはシステムを停止し、データベース側のパスワード変更を行った上でシステムの設定ファイル等を変更し、動作確認をする必要があります。ただデータベースユーザーのパスワード変更をするというだけにも関わらず、直接的な価値を提供しないサービス停止を伴うメンテナンス作業を行う必要があります。これをごく短い一定期間ごとに全てのサービスで実施するのは非常に煩わしいと思います。 今回はこの課題をどのように解決したのか、具体的な事例を含めて紹介させていただきます。 ソリューションの検討 今回は大きく2つのソリューションを検討しました。 MySQL Dual Password の機能を使用 Secrets Manager のローテーション機能 を活用する MySQL Dual Password MySQL 8.0.14 以降 MySQL では Dual Password 機能を利用することができます。この機能を利用することで、プライマリとセカンダリの二つのパスワードを設定し、システムやアプリケーションの停止時間なしにパスワードの変更を行うことが可能となります。 Dual Password 機能を使うための簡単な手順は下記の通りです。 ALTER USER 'user'@'host' IDENTIFIED BY 'new_password' RETAIN CURRENT PASSWORD; で新しいプライマリパスワードを設定し、現在のパスワードをセカンダリとして保持する 全てのアプリケーションが新しいパスワードで接続するように更新する ALTER USER 'user'@'host' DISCARD OLD PASSWORD; でセカンダリパスワードを破棄する Secrets Manager のローテーション機能 AWS Secrets Manager はシークレットの定期的な自動更新をサポートしています。シークレットローテーションを有効にすることで手動でのパスワード管理の負担を軽減できるだけでなく、セキュリティ強化にも大きく寄与できます。 シークレットローテーションを有効にするにはシークレットにローテーションポリシーを設定し、ローテーション用の Lambda 関数を指定する必要があります。 ![ローテーション設定画面](/assets/blog/authors/_awache/20240812/rotation_setting.png =750x) Lambda ローテーション関数 ローテーション関数を作成 AWS によって提供されたコードを自動デプロイすることが可能なので個別に Lambda 関数を作成せずともすぐに利用することができます アカウントからローテーション関数を使用 独自で作成した Lambda 関数を使用することができます。もしくは上記の「ローテーション関数を作成」で作成した関数を再利用したい場合にこちらを選択することが可能です ローテーション戦略 シングルユーザー 一つのユーザーに対してパスワードローテーションを行う方式です ローテーション中にデータベース接続は維持され、適切な再試行戦略によって認証情報の更新とデータベースへのアクセス拒否リスクを低減することが可能です 新しい接続はローテーション後に新しい認証情報(パスワード)を使用する必要があります 交代ユーザー この交代ユーザー戦略はマニュアルを見てもイメージが掴みづらいものでした、が頑張って言語化すると下記のような形になるかなと思います 1つのシークレット内で 2つのユーザーの認証情報(ユーザー名とパスワードの組み合わせ) を交互に更新し、最初のローテーションで 2つ目のユーザー(クローン)を作成し、以降のローテーションでパスワードを切り替える方式です データベースの高可用性を必要とするアプリケーションに適しており、ローテーション中もアプリケーションは引き続き有効な認証情報セットを取得可能です クローンユーザーが元のユーザーと同じアクセス権を持つため、権限変更時には両ユーザーの権限を同期させる必要があるので注意が必要となります イメージを載せてみます ローテーション前後の変化 ![ローテーション実行前後](/assets/blog/authors/_awache/20240812/rotation_exec.png =750x) 少しわかりづらいのですが、上記の図のようにパスワードローテーションが走るとユーザー名に「_clone」が付きます。 初回の場合は、データベース側にも既存のユーザーと同じ権限を持った別のユーザーが作成されます 2回目以降のローテーションではそれを使い回してパスワード更新をし続ける形になります ![交代ユーザー](/assets/blog/authors/_awache/20240812/multi_user_rotation.png =750x) ソリューションの決定 私たちは下記の理由から Secrets Manager のローテーション機能を使用することを決定しました。 設定の容易さ MySQL Dual Password パスワード変更用のスクリプトを準備した上で、変更された内容をアプリケーションに反映する必要がある Secrets Manager のローテーション機能 サービスが必ず Secrets Manager から接続情報を取得している前提であればプロダクト側は特にコード修正等は必要ない 網羅性 MySQL Dual Password MySQL 8.0.14 以降 (Aurora 3.0以降) にのみ対応 Secrets Manager のローテーション機能 KTC で扱っている全ての RDBMS に対応 Amazon Aurora Redshift データベース Password 以外にも対応 プロダクトで使用する API Key なども対応可能 プロジェクトの開始に向けて プロジェクトを開始するに当たって自分たちが何をして何をしないのか、の輪郭を掴むため私たちは最初にコスト、セキュリティ、リソースに対する責任分解点の明確化とやるべきことの設定、インセプションデッキを作成しています。 こちらを簡単に紹介させていただきます。 責任分解点 項目 プロダクト DBRE コスト • DB パスワード格納用の Secrets Manager のコスト • シークレットローテーションを行うための仕組みに関するコストは DBRE で負担する セキュリティ • この仕組みを使うプロダクトは必ず Secrets Manager から データベース接続情報を取得しなければならない • ローテーションが行われた後、次のローテーションが行われるまでにアプリケーションの再デプロイなどで Secrets Manager から接続情報を取得し直さなければならない • 会社で定められたガバナンス制約の基準内にローテーションが完了すること • シークレットローテーションの実績を必要に応じてセキュリティチームに提供できること • 履歴管理等の目的でパスワードを平文で保存しないこと • ローテーションに必要な仕組みのセキュリティが十分であること リソース • データベースに登録されたユーザーは必ず Secrets Manager で管理されていること • シークレットローテーションで実施されるリソースは必要最小限な状態で実行されること やるべきこと 会社で定められたガバナンス制約の基準内にシークレットローテーションが行われること シークレットローテーションの開始、終了、成功、失敗を検知し、それを担当プロダクトに通知すること シークレットローテーションが失敗した場合にプロダクトへの影響がない状態でリカバリを完了すること 同じ DB Cluster に登録されているユーザーに設定されるローテーションのタイミングは同じであること 会社で定められたガバナンス制約の基準にどれだけ則っているかがわかること インセプションデッキ (一部) 我々はなぜここにいるのか 我々は、会社のセキュリティポリシーに準拠し、データベースのパスワードを一定期間内に自動でローテーションするシステムを開発し、導入するためにここにいます。 この自動化プロセスは、セキュリティの強化、管理の手間の軽減、およびコンプライアンスの遵守を目的としています。 このプロジェクトは、DBREチームによって主導され、AWSのローテーション戦略を利用することで、安全かつ効率的なパスワード管理を実現します。 エレベーターピッチ セキュリティ違反のリスクを軽減し、コンプライアンス要件を維持したい プロダクト担当者およびセキュリティグループ向けの、 シークレットローテーションというサービスは、 データベースパスワード管理ツールです。 これは自動化されたセキュリティ強化と管理の手間を削減する機能があり、 MySQL の Dual Password とは違って、 AWS の提供するすべての RDBMS に適用する機能が備わっています。 そしてAWSのサービスを利用する企業だからこそ、最新のクラウド技術を駆使し、柔軟かつスケーラブルなセキュリティ対策を提供し、企業のデータ保護基準に応えることができます。 PoC PoC を行うため、自分たちの検証環境にシークレットローテーションに必要なリソース(DB Cluster / Secrets) を作成し、コンソールからローテーションの仕組みを実施したところすんなりと実用に適しているということが見て取れたのでこれはすぐに提供できる、と大きな期待を持てました。 ただ。。この時の私は知らなかったのです、この後に起こる困難 (悲劇) を。。。 アーキテクチャ シークレットローテーションを提供するためにはこれだけでは不十分なので通知の仕組みをユーザーに提供する必要があります。この仕組みを載せたアーキテクチャを簡単に紹介させていただきます。 シークレットローテーションの全体像 ![全体アーキテクチャ](/assets/blog/authors/_awache/20240812/secrets_rotation_archtecture.png =750x) シークレットローテーションは各 Secrets Manager に登録されたシークレット毎に実行されます わかりやすいように 1ヶ月毎の更新を例にします この場合 1ヶ月に 1度ローテーション実行となることで最大 2ヶ月は同じパスワードを利用することが可能となります その間になんらかのリリースに伴うデプロイのし直しをするだけで気づいたら会社の定めるローテーションルールに乗っかっている状態を実現することができます ローテーションの結果を DynamoDB へ格納 シークレットローテーションではステータスを下記のタイミングで CloudTrail にイベントが書き込まれます 処理開始: RotationStarted 処理失敗: RotationFailed 処理終了: RotationSucceeded 他にもありますが詳しい情報は ローテーションのログエントリ をご確認ください これらのイベントをトリガーとして通知用の Lambda が実行されるように CloudWatch Event を設定します 下記は実際に利用している Terraform のコードの一部です cloudwatch_event_name = "${var.environment}-${var.sid}-cloudwatch-event" cloudwatch_event_description = "Secrets Manager Secrets Rotation. (For ${var.environment})" event_pattern = jsonencode({ "source" : ["aws.secretsmanager"], "$or" : [{ "detail-type" : ["AWS API Call via CloudTrail"] }, { "detail-type" : ["AWS Service Event via CloudTrail"] }], "detail" : { "eventSource" : ["secretsmanager.amazonaws.com"], "eventName" : [ "RotationStarted", "RotationFailed", "RotationSucceeded", "TestRotationStarted", "TestRotationSucceeded", "TestRotationFailed" ] } }) 格納されたローテーション結果はエビデンスとしてセキュリティチームに提出するという用途にも活用されます ここまでの部分を反映したアーキテクチャは下記のようになります。 ![シークレットローテーションのみのアーキテクチャ](/assets/blog/authors/_awache/20240812/secrets_rotation_archtecture2.png =750x) 機能提供に伴って準備が必要な主な AWS リソース 交代ユーザー戦略適用のための Lambda 関数 (MySQL 用と Redshift 用で別々の Lambda が必要) Secrets Manager に設定する交代ユーザー用 Lambda 関数 社内のインフラ構築ルールに準拠するため、Lambda 関数設定や IAM 等、AWS によって自動で生成される Lambda 関数では対応しきれない要素が多くあったため、自分たちで作成 シングルユーザー戦略適用のための Lambda 関数 (MySQL 用と Redshift 用で別々の Lambda が必要) Secrets Manager に設定するシングルユーザー用 Lambda 関数 管理者用ユーザー用のパスワードには交代ユーザー戦略の適用ができない シークレットローテーション結果通知用 Lambda 関数 シークレットローテーションによってローテーションされたことを通知する仕組みは自分たちで用意する必要がある CloudTrail に状態や結果が格納されますのでそれをトリガーとして Slack 通知する イベントトリガーで実行すると Lambda は別々に実行されることに注意 ローテーション結果格納用 DynamoDB ローテーション結果を DynamoDB に格納 通知をする際にどの Slack 通知の関連なのかを明確にし、Slack のスレッドに格納するため TimeStamp も同時に格納 シークレットローテーション用の Lambda 関数を自分たちで管理した理由 前提として私たちは AWS が提供している Lambda を活用しています。 上述したとおりAWS によって提供されたコードを自動デプロイすることが可能なので個別に Lambda 関数を作成せずともすぐに利用することができます。 ただ、私たちは一度コードセットを自分たちのリポジトリに commit した上で terraform で構築しています。 その主な理由は下記のとおりです。 KTC の AWS アカウントには複数のサービスが共存している 複数のサービスが同じ AWS アカウント上に共存していると IAM の権限が強くなりすぎてしまう また複数のリージョンにまたがってサービスを展開している Lambda はクロスリージョンで実行することができないため、同じコードを Terraform を活用し複数のリージョンにデプロイする必要がある シークレットローテーション設定対象のデータベースユーザーの数が多い DB Cluster 数: 200弱、DBユーザー数: 1000弱 全てのシークレットに手動で構築していくのは管理工数が非常に大きくなってしまう 社内ルールの適用 IAM だけでなく、Tag の設定が必須となる 個別で自動作成してしまうとその後 Tag を設定する、という作業が必要となる タイミングによって AWS 側で提供するコードがアップデートされる AWS が提供するコードなので当然これは発生し得ます 場合によってはそのアップデートによってトラブルが発生してしまうこともある可能性があります いくつか書きましたが簡単に言うと社内管理上自分たちでコード管理できた方が都合が良かった、と言うことになります。 Secrets Rotation 用の Lambda 関数を自分たちで管理する方法 ここは本当に大変でした。 最初は AWS から Lambda コードのサンプル が出ているので簡単にいくかと思ったのですがこれをベースにデプロイを行っても様々なエラーが発生してしまいました。自分たちの検証環境ではうまく行っても特定の DB Cluster でのみ発生するエラーなどもあり困難を極めました。 コンソールから自動生成したコードでは発生せず安定していたためこれをうまく活用できるようにする必要があります。 やり方としてはいくつかあるのですが、私たちが試した方法を共有します。 サンプルコードからデプロイする方法を模索する コードそのものは上述のリンクから確認することができます ただし、必要なモジュールをバージョンも含めて全て合わせるのは困難です、またこの Lambda コードは割と頻繁に更新されているのでそれに追従する必要があります これはちょっと大変だったので断念しました さらにこのコードを管理し続けるとなるならば自分たちで別の方法で内製した方がいい気がしました シークレットローテーション関数をコンソールから自動生成した上でその Lambda コードをダウンロードする 毎回コードを自動生成した上でそれをローカルにダウンロードし、自分たちの Lambda に適用する方法でそこまで難しくはありません ただし、自動生成するタイミングで既存で動いているコードとダウンロードしたコードが変わってしまう可能性があります これでも良かったのですが、コード変更を毎回最新化するために一度デプロイをしなければいけないのは自動化するためには少し億劫でした シークレットローテーション関数をコンソールから自動生成したときに裏側で実行される CloudFormation のテンプレートからそのデプロイ方法を確認する コンソールから自動生成すると裏側で AWS の用意した CloudFormation が走ります この時のテンプレートを確認すると AWS が自動生成するコードの S3 のパスを取得することができます。 S3 内にある Zip ファイルを直接取得することで毎回シークレットローテーションのコードを生成するプロセスを削減するメリットを考えると 3 の方法が最も効率的かなと考え今回はこちらを採用しました。 実際に S3 からダウンロードするスクリプトは下記のとおりです。 #!/bin/bash set -eu -o pipefail # Navigate to the script directory cd "$(dirname "$0")" source secrets_rotation.conf # Function to download and extract the Lambda function from S3 download_and_extract_lambda_function() { local s3_path="$1" local target_dir="../lambda-code/$2" local dist_dir="${target_dir}/dist" echo "Downloading ${s3_path} to ${target_dir}/lambda_function.zip..." # Remove existing lambda_function.zip and dist directory rm -f "${target_dir}/lambda_function.zip" rm -rf "${dist_dir}" if ! aws s3 cp "${s3_path}" "${target_dir}/lambda_function.zip"; then echo "Error: Failed to download file from S3." exit 1 fi echo "Download complete." echo "Extracting lambda_function.zip to ${dist_dir}..." mkdir -p "${dist_dir}" unzip -o "${target_dir}/lambda_function.zip" -d "${dist_dir}" cp -p "${target_dir}/lambda_function.zip" "${dist_dir}/lambda_function.zip" echo "Extraction complete." } # Create directories if they don't exist mkdir -p ../lambda-code/mysql-single-user mkdir -p ../lambda-code/mysql-multi-user mkdir -p ../lambda-code/redshift-single-user mkdir -p ../lambda-code/redshift-multi-user # Download and extract Lambda functions download_and_extract_lambda_function "${MYSQL_SINGLE_USER_S3_PATH}" "mysql-single-user" download_and_extract_lambda_function "${MYSQL_MULTI_USER_S3_PATH}" "mysql-multi-user" download_and_extract_lambda_function "${REDSHIFT_SINGLE_USER_S3_PATH}" "redshift-single-user" download_and_extract_lambda_function "${REDSHIFT_MULTI_USER_S3_PATH}" "redshift-multi-user" echo "Build complete." デプロイの際にこのスクリプトを流せばコードの最新化が可能となります。逆に言うとこのスクリプトを実行しなければコード自体はこれまで動いていたものを継続して使用し続けることができます。 シークレットローテーション結果通知用 Lambda 関数と DynamoDB シークレットローテーション結果通知は CloudTrail の PUT をトリガーとして実行されます。シークレットローテーションの Lambda に手を加えればもうちょっと簡単にできたかと思うのですがそれでは何のために AWS の提供しているコードを最大限利用しようとしていたのか分かりません。 開発する前の私は PUT トリガーで通知を行えばいいだけ、と簡単に考えていました。ただ、そんなに甘くはありませんでした。 ここで再度全体像を確認してみましょう。 ![全体アーキテクチャ](/assets/blog/authors/_awache/20240812/secrets_rotation_archtecture.png =750x) 通知としては下記のように開始時に Slack の通知用のスレッドを作り、終了時にはそのスレッドに追記する形で通知を行います。 ![Slack 通知](/assets/blog/authors/_awache/20240812/slack_notification.png =750x) 今回利用するイベントは下記のとおりです。 処理開始時のイベント Cloud Trail に PUT されるイベント: RotationStarted 処理終了時のイベント 処理成功時に Cloud Trail に PUT されるイベント: RotationSucceeded 処理失敗時に Cloud Trail に PUT されるイベント: RotationSucceeded 処理開始時のイベントである RotationStarted の際にはその Slack のタイムスタンプを DynamoDB に格納し、それを使うことでスレッドに追記することができます。 これを考慮すると DynamoDB がどの単位でユニークになるかを検討する必要があります。結果としては Secrets Manager の SecretID、そして次回のローテーション予定日を組み合わせることでユニークにすることとしました。 DynamoDB の構成の主要なカラム構成は下記のとおりです。 (実際にはもっと多く、様々な情報を入れています) SecretID: パーテションキー NextRotationDate: ソートキー 次回ローテーション予定日、describe で取得可能 SlackTS: RotationStarted のイベントの際、Slack で最初に送ったタイムスタンプ このタイムスタンプを利用することで Slack のスレッドに追記することができる VersionID: RotationStarted のイベントの際の SecretID のバージョン 万が一トラブルが発生した場合すぐに戻せるように一つ前のバージョンを保持しておくことでローテーション前のパスワード情報を復元することが可能 最も困った点は、シークレットローテーションの一回の処理の中で複数回 Cloud Trail に PUT するため、ステップごとに別々の Lambda が起動されることです。頭では理解していたものの、これは実際には非常に面倒でした。 そのため下記を考慮しなければなりませんでした。 シークレットローテーションの処理自体は非常に高速な処理 Cloud Trail に PUT されるタイミングが RotationStarted と RotationSucceeded (もしくはRotationFailed) でほぼ同じくらいなので通知用の Lambda の実行もほぼ同時に 2回流れることになる 通知用の Lambda では Slack 通知や DynamoDB への登録も行っているため、RotationStarted の処理が完了する前に処理終了時のイベントが流れてしまうことがある これが発生するとどのスレッドに送るべきなのかが定まらず新規で Slack に投稿されてしまう 解決の方法としてはシンプルにイベント名が RotationStarted でなかった場合、Slack に通知する処理を数秒待つ、ということで対応しました。 設定ミス等でシークレットローテーションが失敗してしまうことがあります。ほとんどの場合は DB のパスワードが更新される前にエラーとなるのでプロダクトにすぐに影響があるわけではありません。 その際には下記のコマンドでリカバリを実施します。 # VersionIdsToStages が AWSPENDING のバージョン ID を取得 $ aws secretsmanager describe-secret --secret-id ${secret_id} --region ${region} - - - - - - - - - - Versions 出力例 - - - - - - - - - - "Versions": [ { "VersionId": "7c9c0193-33c8-3bae-9vko-4129589p114bb", "VersionStages": [ "AWSCURRENT" ], "LastAccessedDate": "2022-08-30T09:00:00+09:00", "CreatedDate": "2022-08-30T12:53:12.893000+09:00", "KmsKeyIds": [ "DefaultEncryptionKey" ] }, { "VersionId": "cb804c1c-6d1r-4ii3-o48b-17f638469318", "VersionStages": [ "AWSPENDING" ], "LastAccessedDate": "2022-08-30T09:00:00+09:00", "CreatedDate": "2022-08-30T12:53:22.616000+09:00", "KmsKeyIds": [ "DefaultEncryptionKey" ] } ], - - - - - - - - - - - - - - - - - - - - - - - - # 該当のバージョンを削除 $ aws secretsmanager update-secret-version-stage --secret-id ${secret_id} --remove-from-version-id ${version_id} --version-stage AWSPENDING --region ${region} # コンソールから該当のシークレットを 「すぐにローテーションさせる」 今のところ発生はしていないのですが、万が一トラブルが発生し、DB のパスワード変更がされてしまった時には下記のコマンドを実行し、過去のパスワードを取得します。 とはいえこちらも交代ユーザーローテーションなのですぐにプロダクトから データベースに接続できなくなるわけではなく、次のローテーションが実行されるまでは基本的には問題ないと考えています。 $ aws secretsmanager get-secret-value --secret-id ${secret_id} --version-id ${version_id} --region ${region} --query 'SecretString' --output text | jq . # user と password は aws secretsmanager get-secret-value で取得したパラメータを設定する $ mysql --defaults-extra-file=/tmp/.${管理用DBユーザー名}.cnf -e "ALTER USER ${user} IDENTIFIED BY '${password}' # 接続確認 $ mysql --defaults-extra-file=/tmp/.user.cnf -e "STATUS" ここまででやるべきことのうち、下記を達成する基盤を作ることができました。 シークレットローテーションの開始、終了、成功、失敗を検知し、それを担当プロダクトに通知すること シークレットローテーションが失敗した場合にプロダクトへの影響がない状態でリカバリを完了すること 私たちの戦いはここでは終わらなかった 上記で主要機能を構築できたのですが、私たちがやるべきことはあと3つ残っています。 会社で定められたガバナンス制約の基準内にシークレットローテーションが行われること 同じ DB Cluster に登録されているユーザーに設定されるローテーションのタイミングは同じであること 会社で定められたガバナンス制約の基準にどれだけ則っているかがわかること これらを実現するために周辺の機能を開発する必要がありました。 会社で定められたガバナンス制約の基準にどれだけ則っているかがわかる仕組みの構築 これでやることは簡単に言うと全ての DB Cluster に存在するすべてのユーザーのリストを取得すること、そしてそのユーザーのパスワードの更新日がガバナンスで定められた期間内であるか、を確認することです。 各 DB Cluster にログインして下記のクエリを実行することで、ユーザーごとのパスワードの最終更新日を取得することができます。 mysql> SELECT User, password_last_changed FROM mysql.user; +----------------+-----------------------+ | User | password_last_changed | +----------------+-----------------------+ | rot_test | 2024-06-12 07:08:40 | | rot_test_clone | 2024-07-10 07:09:10 | : : : : : : : : +----------------+-----------------------+ 10 rows in set (0.00 sec) これをすべての DB Cluster で実行するわけですが、私たちはすでに日次ですべての DB Cluster のメタ情報を取得し、ER図や my.cnf を自動生成したり、不適切な設定が DB に存在していないかをチェックするスクリプトを実行しています。 ここにユーザー一覧とパスワードの最終更新日を取得して DynamoDB に保存する、という処理を追加するだけで解決できました。 DynamoDB の構成の主要なカラム構成は下記のとおりです。 DBClusterID: パーテションキー DBUserName: ソートキー PasswordLastChanged: パスワード最終更新日 実際には RDS を使用する上で自分たちがコントロールしない、自動的に作成されるユーザー シークレットローテーションの機能によって作成される「_clone」という名前を持つユーザー を弾く必要があります。そのため本当に必要なデータは下記のクエリで取得しています。 SELECT CONCAT_WS(',', IF(RIGHT(User, 6) = '_clone', LEFT(User, LENGTH(User) - 6), User), Host, password_last_changed) FROM mysql.user WHERE User NOT IN ('AWS_COMPREHEND_ACCESS', 'AWS_LAMBDA_ACCESS', 'AWS_LOAD_S3_ACCESS', 'AWS_SAGEMAKER_ACCESS', 'AWS_SELECT_S3_ACCESS', 'AWS_BEDROCK_ACCESS', 'rds_superuser_role', 'mysql.infoschema', 'mysql.session', 'mysql.sys', 'rdsadmin', ''); その上で DynamoDB の情報を集計する SLI 用の Lambda を作りました。結果としてはこんな形で出力しています。 ![SLI 通知](/assets/blog/authors/_awache/20240812/sli.png =750x) こちらの出力内容は下記のとおりです。 Total Items: すべての DB Cluster に存在するすべてのユーザーの数 Secrets Exist Ratio: KINTO テクノロジーズで使用する Secrets Manager に登録する命名規則にあった SecretID が存在する割合 Rotation Enabled Ratio: シークレットローテーションの機能が有効化されている割合 Password Change Due Ratio: 会社のガバナンスルールに則っているユーザーの割合 重要なことは Password Change Due Ratio が 100 % になることです。ここが満たされさえすればシークレットローテーションの機能を使う必要もありません。 この SLI 通知の仕組みによって下記を達成することができました。 会社で定められたガバナンス制約の基準にどれだけ則っているかがわかること 同じ DB Cluster に登録されているユーザーに設定されるローテーションを同じタイミングにするための仕組み これを仕組み化するためには二つのコードセットを書く必要がありました。 DBClusterID 毎のローテーション実行時間を決定させるための仕組み Secrets Manager に上記で決定した時間でローテーションを設定するための仕組み それぞれについて説明します。 DBClusterID 毎のローテーション実行時間を決定させるための仕組み 前提としてシークレットローテーションの実行時間は ローテーションウィンドウ と呼ばれるスケジュールで記載することができます。ローテーションウィンドウの記載方式と用途は大きく下記の2つです。 rate 式 ローテーション間隔を指定の日数で実行したい場合に使用 cron 式 特定の曜日、特定の時間に実行したいなど少し細かく指定をしたい場合はこちらを使用 私たちは平日日中帯に実行したかったこともあり、cron 式を用いて設定することとしました。 もう一つ設定すべき点はローテーションで設定する「ウィンドウ期間」です。これら二つを組み合わせてある程度ローテーションの実行タイミングをコントロールすることができます。 ローテーションウィンドウとウィンドウ期間の関係は下記のとおりです。 ローテーションウィンドウは開始時間ではなくローテーションが完了する時間 ウィンドウ期間はローテーションウィンドウで設定された時間に対してどれくらいの猶予を持たせて実行をするか ウィンドウ期間のデフォルトは 24時間 つまり、ローテーションウィンドウを毎月第4火曜日の10:00 に設定して、ウィンドウ期間を何も指定しない(24時間)とシークレットローテーションが実行されるタイミングは 毎月第4月曜日の 10:00 ~ 毎月第4火曜日の 10:00 の間のいずれかで実行されることになる となります。これは直感的に難しいのですが、この関係を理解していないと予想もしないタイミングでシークレットローテーションが実行されてしまいます。 以上の前提を念頭に置きつつ、要件を下記のとおり定めました。 DBClusterID 毎に、複数の DB ユーザーのローテーションが同じ時間帯に実行される ウィンドウ期間は 3時間とする あまり短いタイミングで設定すると万が一トラブルが発生した時のリカバリまでの時間帯に同時多発的に問題が出てしまう可能性がある 実行のタイミングは平日火曜から金曜の 09:00 ~ 18:00 の間とする 月曜日は祝日の可能性が高いため実行しない ウィンドウ期間の時間を 3時間で固定することとするため、cron 式に設定できるのは 12:00 ~ 18:00 の 6時間 cron 式に設定できるのは UTC のみ 可能な限り実行のタイミングをバラバラに設定する 同じタイミングで多くのシークレットローテーションが走ると各種 API の制限に影響を与えてしまう可能性がある 何かしらのエラーが発生した場合、一気にアラートが来ることで対応に追われてしまう Lambda 処理全体の流れとしては下記のような形になります。 データの取得 : DynamoDBから DBClusterID のリストを取得 DynamoDBから既存のシークレットローテーションの設定情報を取得 スケジュールの生成 : 週、曜日、時間のすべての組み合わせ(スロット)を初期化 対象のDBClusterID が既存のシークレットローテーションの設定情報に存在しないか確認 存在していたらその DBClusterID を既存のシークレットローテーションの設定情報と同じスロットに埋め込む 新しい DBClusterID をスロットに均等に分配する スロットに空きがあればそこに新しいデータを追加し、空きがなければ次のスロットにデータを追加 DBClusterID のリストの最後まで繰り返し実行 データの保存 : 既存のデータと重複しない新しいシークレットローテーションの設定情報をフィルタリングして保存します。 エラーハンドリングと通知 : 重大なエラーが発生した場合、Slackにエラーメッセージを送信して通知します。 これによって格納される DynamoDB のカラムは下記のとおりです。 DBClusterID: パーテションキー CronExpression: シークレットローテーションに設定する cron 式 少し分かりづらいのですがイメージとしては下記のような状態になるようにしています。 ![スロット投入イメージ](/assets/blog/authors/_awache/20240812/decide.png =750x) ここまでで DBClusterID 毎のローテーション実行時間を決定させるための仕組みができました。 これでは実際にシークレットローテーションの設定をすることはできません。なので実際にシークレットローテーションを設定する仕組みが必要となります。 Secrets Manager に上記で決定した時間でローテーションを設定するための仕組み 私たちはシークレットローテーションの仕組みだけが会社のガバナンスを守る手段だと思っていません。重要なことは会社で定められたガバナンス制約の基準が満たされていることです。そのためこの仕組みを必ず使う、という強制力を持たせるのではなく DBRE が考えた最も安全で最も簡単な仕組みとしてユーザーが使いたいと思えば使ってもらえる、そんな仕組みです。 もしかしたら DBCluster にあるユーザーの中でこのユーザーはシークレットローテーションで、このユーザーは別の方法で自分たちで管理したい、そのような要望が出てくる可能性もあります。 これを満たすためには必要な DBClusterID に紐づく データベースユーザーの単位でシークレットローテーションの設定をするコマンドラインツールが必要となります。 私たちは DBRE として日頃から行う作業をコマンドライン化した dbre-toolkit というツールを開発していました。例えば Point In Time Restore を簡単に行えるツール、Secrets Manager にある DB 接続ユーザーの情報を取得して defaults-extra-file を作成するツールなどがパッケージ化されて一つにまとまっているものです。 今回はここに一つサブコマンドを追加しました。 % dbre-toolkit secrets-rotation -h 2024/08/01 20:51:12 dbre-toolkit version: 0.0.1 指定された Aurora Cluster に紐づく Secrets Rotation スケジュールに基づいて Secrets Rotation を設定するコマンドです。 Usage: dbre-toolkit secrets-rotation [flags] Flags: -d, --DBClusterId string [Required] 対象サービスの DBClusterId -u, --DBUser string [Required] 対象の DBUser -h, --help help for secrets-rotation ここで指定された DBClusterID と DBUser の組み合わせを DynamoDB から取得してその情報を Secrets Manager に登録することでシークレットローテーションの設定を完了させる、というものです。 これによって下記を達成することができました。 会社で定められたガバナンス制約の基準内にシークレットローテーションが行われること 同じ DB Cluster に登録されているユーザーに設定されるローテーションのタイミングは同じであること そしてここまでやってようやく自分たちが定めたやるべきことの全てを完了させることができました。 まとめ ここまで私たちが実現したことは下記のとおりです。 シークレットローテーションの開始、終了、成功、失敗を検知し、それを担当プロダクトに通知すること CloudTrail に Put されるイベントを検知して適切に通知する仕組みの開発 シークレットローテーションが失敗した場合にプロダクトへの影響がない状態でリカバリを完了すること トラブル対応手順を準備 シークレットローテーションの仕組みを理解することで、基本的にはシークレットローテーションが行われて即座に致命的なエラーになる可能性は少ないことがわかった 会社で定められたガバナンス制約の基準内にシークレットローテーションが行われること SLI 通知用の仕組みの開発 シークレットローテーションの設定を確実にできるような設定ツールの開発 同じ DB Cluster に登録されているユーザーに設定されるローテーションのタイミングは同じであること DBClusterID 単位でシークレットローテーションに設定する cron 式を DynamoDB に保存する仕組みを開発 会社で定められたガバナンス制約の基準にどれだけ則っているかがわかること SLI 通知用の仕組みの開発 全体像としては下記のような形になりました。 ![全体像](/assets/blog/authors/_awache/20240812/secrets_rotation_overview.png =750x) 想像以上に複雑です。。私たちはマネージドでシークレットローテーションを実現することをある意味簡単に考えすぎていたとも言えます。 AWS の提供するシークレットローテーションの機能は単純に利用するだけならすぐにできるとても強力な仕組みです。 ただ、私たちが実現したいことを本気でやろうとすると一筋縄ではいかず様々な要素を自分たちで内製する必要があります。ここに至るまでには本当にさまざまなトライアンドエラーがありました。 こうしてできたシークレットローテーションの仕組みを利用して KTC の データベースが誰でも簡単に、そして誰も気にしなくてもいい感じに安全に運用し続けられる、そんな環境を作っていければと思っています。 KINTO テクノロジーズ DBRE チームでは一緒に働いてくれる仲間を絶賛募集中です!カジュアル面談も歓迎ですので、 少しでも興味を持っていただけた方はお気軽に  X の DM  等でご連絡ください。併せて、 弊社の採用 X アカウント  もよろしければフォローお願いします!
アバター
Hello, I am _awache ( @_awache ), from DBRE at KINTO Technologies (KTC). In this article, I’ll provide a comprehensive overview of how I implemented a safe password rotation mechanism for database users primarily registered in Aurora MySQL, the challenges I encountered, and the peripheral developments that arose during the process. To start, here's a brief summary, as this will be a lengthy blog post. Summary Background Our company has implemented a policy requiring database users to rotate their passwords at regular intervals. Solution Considered MySQL Dual Password: To set primary and secondary passwords by using Dual Password function that is available in MySQL 8.0.14 and later. AWS Secrets Manager rotation function: To enable automatic update of passwords and strengthened security by using Secrets Manager Adopted Rotation function of AWS Secrets Manager was adopted for its easy setting and comprehensiveness. Project Kickoff At the beginning of the project, we created an inception deck and clarified key boundaries regarding cost, security, and resources. What was developed in this project Lambda functions After thorough research, we developed multiple Lambda functions because the AWS-provided rotation mechanism did not fully meet KTC's requirements. Lambda function for single user strategy Purpose: To rotate passwords for a single user Settings: Managed by Secrets Manager. These functions execute at the designated rotation times in Secrets Manager to update passwords. Lambda function for alternate users rotation strategy Purpose: This function updates passwords for two users alternately to enhance availability. Settings: Managed by Secrets Manager. In the initial rotation, a second user (a clone) is created; passwords are switched in subsequent rotations. Lambda function for Secret Rotation Notifications Purpose: this function reports the results of secret rotations. Trigger: CloudTrail events for RotationStarted, RotationSucceeded, and RotationFailed Function: To store the rotation results in DynamoDB and send notifications to Slack. Additionally, it posts a follow-up message with a timestamp to the Slack thread. Lambda function for Managing DynamoDB storage of rotation results Purpose: To store rotation results in DynamoDB as evidence for submission to the security team. Function: Executes in response to CloudTrail events to save the rotation results to DynamoDB and send SLI notifications based on the stored data. Lambda function for SLI notification Purpose: To monitor the status of rotation and to send SLI notifications. Function: Retrieves information from DynamoDB to track the progress of secret rotation and sends notifications to Slack as needed. Lambda function for rotation schedule management Purpose: To determine the execution time of rotation for a DBClusterID. Function: Generates a new schedule based on the settings of existing secret rotations, saves it to DynamoDB, and sets the rotation window and duration. Lambda function for applying rotation settings Purpose: To apply the scheduled rotation settings to Secrets Manager Function: Configures secret rotation at the designated times using information from DynamoDB. A Tool for Registering Secret Rotations We developed an additional tool to facilitate local registration of secret rotations. Tool for setting Secrets Rotation schedule Purpose: To set secret rotation schedules per database user. Function: Applies the secret rotation settings based on data saved in DynamoDB for the specified DBClusterID and DBUser. Final Architecture Overview We initially believed it could be done much simpler, but it turned out to be more complex than expected... ![The whole image](/assets/blog/authors/_awache/20240812/secrets_rotation_overview.png =750x) Results Automated the entire secret rotation process, reducing security and management efforts. Developed a comprehensive architecture that meets governance requirements. Leveraged secret rotation to enhance database safety and efficiency, with ongoing improvement efforts. Now, let's explore the main story. Introduction KTC has implemented a policy requiring database users to rotate their passwords at regular intervals . However, rotating passwords is not a straightforward process. To change a database user's password, the system must first be stopped. Then, the password in the database is updated, system settings files are adjusted, and finally, system operations must be verified. In other words, we need to perform a maintenance operation that provides no direct value by stopping the system just to change a database user's password. It would be highly inconvenient to perform this for every service at extremely short intervals. This article explains how we addressed this challenge through specific examples. Solution Considerations We considered two major solutions. To use functions of MySQL Dual Password To make use of the rotation function of Secrets Manager MySQL Dual Password The Dual Password function is available in MySQL starting from version 8.0.14. Using this function allows us to set both a primary and a secondary password, enabling password changes without stopping the system or applications. Simple steps to use Dual Password function are as follows: Set a new primary password. You can use the command ALTER USER 'user'@'host' IDENTIFIED BY 'new_password' RETAIN CURRENT PASSWORD; while keeping the current password as the secondary one. Update all applications to be connected with the new password. Delete the secondary password by ALTER USER 'user'@'host' DISCARD OLD PASSWORD; . Rotation function of Secrets Manager AWS Secrets Manger supports periodical automatic update of secrets. Activating secret rotation not only reduces efforts to manage passwords manually but also helps to enhance security. To activate it, one only needs to configure the rotation policy in Secrets Manager and assign a Lambda function to handle the rotation. ![Rotation setting screen](/assets/blog/authors/_awache/20240812/secrets_rotation_overview.png =750x) Lambda rotation function Creating the rotation function By automatically deploying the code provided by AWS, we can use it immediately without the need to create custom Lambda functions. Using rotation function from Account You can either create a custom Lambda function or select the one created earlier under 'Creating the Rotation Function' if you wish to reuse it. Rotation strategy Single user Method to rotate passwords for a single user. The database connection is maintained, allowing authentication information to be updated and reducing the risk of access denial with an appropriate retry strategy. After rotation, new connections require the updated authentication information (password). Alternate user Initially, I found it challenging to grasp the alternate user strategy, even after reading the manual. However, after careful consideration, I’ve articulated it as follows: This method alternates password updates by rotation, where the authentication information (a combination of username and password) is updated in a secret. After creating a second user (a clone) during the initial rotation, the passwords are switched in subsequent rotations. This approach is ideal for applications that require high database availability, as it ensures that valid authentication information is available even during rotations. The clone user has the same access rights as the original user. It's important to synchronize the permissions of both users when updating their access rights. Below is an image illustrating the concept explained above. Changes before and after rotation ![Before/after rotation](/assets/blog/authors/_awache/20240812/rotation_exec.png =750x) Though it may be a bit difficult to see, the username will have '_clone' appended during password rotation. In the first rotation, a new user with the same privileges as the existing user is created on the database side. The password will continue to be updated by reusing it in subsequent rotations after the second one. ![Alternate user](/assets/blog/authors/_awache/20240812/multi_user_rotation.png =750x) The Solution Adopted We decided to use rotation function by Secrets Manager for the following reasons: Easy to set up MySQL Dual Password The updated password must be applied to the application after preparing a script for the password change. Rotation function of Secrets Manager The product side does not need to modify code as long as the service consistently retrieves connection information from Secrets Manager. Comprehensiveness MySQL Dual Password Supported only in MySQL 8.0.14 and later (Aurora 3.0 or later) Secrets Manager Rotation Function Supports all RDBMS used by KTC Amazon Aurora Redshift Providing additional support beyond database passwords Can also manage API keys and other credentials used in the product. Toward the Project Kickoff Before starting the project, we first clarified our boundaries for cost, security, and resources to determine what should and shouldn’t be done. We also created an inception deck. The following is outline of what was discussed: Breakdown of responsibilities Topic Product team DBRE team Cost - Responsible for the cost of Secrets Manager for storing database passwords - Responsible for the cost associated with the secret rotation mechanism. Security - Products using this mechanism must always retrieve database connection information from Secrets Manager. - After a rotation, connection information must be updated by redeploying the application and other components until the next rotation occurs. - Ensuring that rotations are completed within the company's defined governance limits. - Providing records of secret rotations to the security team as required. - Passwords must not be stored in plain text to maintain traceability. - Sufficient security must be maintained in the mechanism used for rotation. Resources - Ensuring that all database users are managed by Secrets Manager. - Ensuring that the implementation of secret rotation resources is executed with the minimum necessary configuration. What needed to be done Execute secret rotation within the company’s defined governance limits. Detect and notify the start, completion, success, or failure of a secret rotation to the relevant product teams. Ensure recovery from a failed secret rotation without affecting the product. Align rotation timing with the schedule set by users registered in the same DB Cluster. Monitor compliance with the company’s governance standards. Inception deck (an excerpt) Why are we here To develop and implement a system that complies with the company’s security policy and automatically rotates database passwords at regular intervals. To strengthen security, reduce management efforts, and ensure compliance through automation. Led by the DBRE team, to achieve safer and more efficient password management by leveraging AWS's rotation strategy. Elevator pitch Our goal is to reduce the risk of security breaches and ensure compliance. We offer a service called Secret Rotation, designed for product teams and the security group, to manage database passwords. It has functions to strengthen automatic security and reduce effort to manage, Unlike MySQL’s Dual Password feature, It is compatible with all AWS RDBMS option Through AWS services, we utilize the latest cloud technologies to provide flexible and scalable security measures that meet enterprise data protection standards. Proof of Concept (PoC) To execute the PoC we prepared the necessary resources in our testing environment, such as a DB Cluster for our own verification. We discovered that implementing the rotation mechanism through the console was straightforward, leading us to anticipate a rapid deployment of the service with high expectations. However, at that time, I had no way of knowing that trouble was just around the corner... Architecture Providing secret rotation alone is not enough without a notification mechanism for users. I’ll introduce an architecture that includes this essential feature. Secret Rotation Overview ![The whole architecture](/assets/blog/authors/_awache/20240812/secrets_rotation_archtecture.png =750x) Secret rotation will be managed by secrets registered in Secrets Manager. Here’s an example of a monthly update for clarity. In this case, the same password can be used for up to 2 months due to the monthly rotation schedule. During this period, you will achieve compliance with the company's rotation rules with minimal effort, aligning with any necessary deployment timing for product releases. Rotation Results to be stored at DynamoDB In Secret Rotation, a status will be written to CloudTrail as an event by the following timing: Process start; RotationStarted Process failure; RotationFailed Process end; RotationSucceeded See log entries for rotation as there are additional details available. We configured a CloudWatch Event so that the above events would serve to trigger the Lambda function for notification. Below are some of the Terraform code snippets used: cloudwatch_event_name = "${var.environment}-${var.sid}-cloudwatch-event" cloudwatch_event_description = "Secrets Manager Secrets Rotation. (For ${var.environment})" event_pattern = jsonencode({ "source" : ["aws.secretsmanager"], "$or" : [{ "detail-type" : ["AWS API Call via CloudTrail"] }, { "detail-type" : ["AWS Service Event via CloudTrail"] }], "detail" : { "eventSource" : ["secretsmanager.amazonaws.com"], "eventName" : [ "RotationStarted", "RotationFailed", "RotationSucceeded", "TestRotationStarted", "TestRotationSucceeded", "TestRotationFailed" ] } }) ``` Stored rotation results can be used as evidence for submission to the security team. The architecture reflecting the components discussed so far is as follows: ![Architecture only for Secret Rotation](/assets/blog/authors/_awache/20240812/secrets_rotation_archtecture2.png =750x) AWS resources needed for providing functions Lambda function for applying alternate user strategy (Different Lambda functions are required for MySQL and Redshift.) Lambda function for alternate user to be set at Secrets Manager We developed this in-house to meet company rules for infrastructure compliance. We encountered several elements that automatically-generated Lambda functions could not address, such as Lambda function settings and IAM configurations. Lambda function to apply single user strategy (Different Lambda is needed for MySQL and Redshift respectively) Lambda function for single user to be set at Secrets Manager A password for administrator user cannot be applied with alternate user strategy Lambda function for Secret Rotation Notifications A mechanism to notify that it has been rotated by Secret Rotation must be prepared by ourselves. As CloudTrail is stored with the status and results, we can use them as a trigger to notify to Slack. Be careful that Lambda will be executed individually when executed by an event trigger. DynamoDB for storing rotation results Results of rotation to be stored in DynamoDB Additionally, the timestamp is stored in the Slack thread to clarify which notification it is related to. Why we chose to manage the Lambda function for secret rotation ourselves As a prerequisite, we use AWS-provided Lambda. Since AWS provides the ability to automatically deploy code, we can use it immediately without the need to create individual Lambda functions. However, we deploy it using Terraform after committing the code set to our repository. Main reasons for this are as follows: Multiple services exist within KTC's AWS account. When several services exist in the same AWS account, IAM’s privilege becomes too strong Also, services are provided across regions As Lambda cannot be executed in cross-region, the same code must be deployed to regions by using Terraform. We have a large number of database users that require Secret Rotation settings. Number of database clusters Below 200; Number of database users Below 1000 The workload would be overwhelming if we manually built the system for each secret. Applying Company Rules It calls for setting of Tag in addition to IAM Automatic and individual creation will require setting up of Tag subsequently AWS-provided code will be updated periodically. Since the codes are provided by AWS, this inevitably happens. There is a possibility that this will lead to a trouble by chance I have written several matters so far, but in a nutshell, it was more convenient for us to manage the codes in consideration of the in-company rules. How we managed Lambda functions for Secrets Rotation This was really a hard job. At the beginning, we thought it would go easily as AWS provided samples of Lambda codes . But we saw many kinds of errors after deploying based on them. While we had some success during our own verification, we faced significant challenges when errors occurred in specific database clusters. However, we discovered that the automatically generated code from the console was error-free and remained stable, highlighting the need to use it effectively. There are several approaches, but let me share the one we tried. Exploring how to deploy from a sample code We can see the code itself from the above mentioned link However, it is hard to match all the necessary modules including version. Besides, this Lambda code is frequency updated and we have to follow up. We gave up this approach as it was a hard job. Then, we realized it would be better off if make it inhouse with other method as long as we need to control this code. Download the Lambda code after automatically generating the Secret Rotation function from the console. This method is to generate code automatically every time, download it to local to apply it to our Lambda. It is not too difficult to do. However, there is a chance that existing and working code may change from a downloaded code by timing of automatic code generation. This approach would have worked, but we found it burdensome to deploy every time the code needed updates. Verify how it was deployed from the CloudFormation template used behind the scenes when the Secret Rotation function is automatically generated from the console. When automatically generated from the console, AWS CloudFormation operates in the background. By examining the template at this stage, we can obtain the S3 path of the code automatically generated by AWS. We adopted the third method above as it was the most efficient way to directly obtain the Zip file from S3, eliminating the need to generate Secret Rotation code each time. The actual script to download from S3 are as follows: #!/bin/bash set -eu -o pipefail # Navigate to the script directory cd "$(dirname "$0")" source secrets_rotation.conf # Function to download and extract the Lambda function from S3 download_and_extract_lambda_function() { local s3_path="$1" local target_dir="../lambda-code/$2" local dist_dir="${target_dir}/dist" echo "Downloading ${s3_path} to ${target_dir}/lambda_function.zip..." # Remove existing lambda_function.zip and dist directory rm -f "${target_dir}/lambda_function.zip" rm -rf "${dist_dir}" if ! aws s3 cp "${s3_path}" "${target_dir}/lambda_function.zip"; then echo "Error: Failed to download file from S3." exit 1 fi echo "Download complete." echo "Extracting lambda_function.zip to ${dist_dir}..." mkdir -p "${dist_dir}" unzip -o "${target_dir}/lambda_function.zip" -d "${dist_dir}" cp -p "${target_dir}/lambda_function.zip" "${dist_dir}/lambda_function.zip" echo "Extraction complete." } # Create directories if they don't exist mkdir -p ../lambda-code/mysql-single-user mkdir -p ../lambda-code/mysql-multi-user mkdir -p ../lambda-code/redshift-single-user mkdir -p ../lambda-code/redshift-multi-user # Download and extract Lambda functions download_and_extract_lambda_function "${MYSQL_SINGLE_USER_S3_PATH}" "mysql-single-user" download_and_extract_lambda_function "${MYSQL_MULTI_USER_S3_PATH}" "mysql-multi-user" download_and_extract_lambda_function "${REDSHIFT_SINGLE_USER_S3_PATH}" "redshift-single-user" download_and_extract_lambda_function "${REDSHIFT_MULTI_USER_S3_PATH}" "redshift-multi-user" echo "Build complete." By running this script at the time of deployment, the code can be updated. Conversely, the conventional code can be used continuously unless running this script. Lambda function and Dynamo DB to notify results of Secret Rotation A notification of Secret Rotation results is executed with PUT of CloudTrail as a trigger. We considered modifying the Lambda function for Secret Rotation to simplify things. However, this would have complicated explaining our effort to fully utilize the code provided by AWS. Before starting development, I initially thought all we needed was to use a PUT trigger for notifications. But, things were not that easy. Let’s see the whole picture again. ![The whole architecture](/assets/blog/authors/_awache/20240812/secrets_rotation_archtecture.png =750x) Its notification process involves creating a Slack notification thread at the start and adding a postscript to the thread when the notification is completed. ![Slack notification](/assets/blog/authors/_awache/20240812/secrets_rotation_overview.png =750x) Events we use this time are as follows: Event at the start of the processing Event of PUT to Cloud Trail RotationStarted Event at the end of the processing Event of PUT to Cloud Trail when the processing succeeds RotationSucceeded Event of PUT to Cloud Trail when the processing fails RotationSucceeded On the occasion of RotationStarted, an event at the start of the processing, its Slack time stamp can be stored in DynamoDB and we can add postscripts on the thread by using it. Considering these, we had to examine by which unit DynamoDB would become unique. Consequently, we chose to combine SecretID of Secrets Manager and scheduled date of the next rotation to make it unique. Main structure of columns of DynamoDB is as follows: (In actual, more information is being stored in them) SecretID: Partition key NextRotationDate: Sort key Schedule of the next rotation; Obtainable with describe SlackTS: Time stamp sent first by Slack at the event of RotationStarted Using this time stamp, we can add postscript on the Slack thread. VersionID: Version of SecretID at the event of RotationStarted By keeping the last version to reverse to the previous state at once if a trouble happens, it is possible to restore the password information before the rotation The biggest challenge we faced was that multiple Lambda functions were triggered in steps due to several PUT events being activated during a single Secret Rotation process. Even though i understood this in theory, it proved to be extremely troublesome. We had to pay attention to the following consequently: Processing of Secret Rotation itself is a very high-speed one. Since the timing of PUT to Cloud Trail is almost identical for RotationStarted and RotationSucceeded (or RotationFailed), the execution of Lambda for notification will take place twice, almost simultaneously. But Lambda for notification also handles Slack notification and DynamoDB registration, an event at the processing end may run before the RotationStarted process completes. When this happens, a new script will be added to Slack without knowing the destination thread. To solve this, we chose a simpler approach where processing to notify Slack should be halted for a couple of seconds in case of the name of event is other than RotationStarted. Secret Rotation may fail due to an error of setting and such. In most cases, a product will not be affected by this at once as it becomes an error before DB password updating. In such a case, a recovery can be executed with the following command. # VersionIdsToStages obtains the version ID of AWSPENDING $ aws secretsmanager describe-secret --secret-id ${secret_id} --region ${region} - - - - - - - - - - Output sample of Versions - - - - - - - - - - "Versions": [ { "VersionId": "7c9c0193-33c8-3bae-9vko-4129589p114bb", "VersionStages": [ "AWSCURRENT" ], "LastAccessedDate": "2022-08-30T09:00:00+09:00", "CreatedDate": "2022-08-30T12:53:12.893000+09:00", "KmsKeyIds": [ "DefaultEncryptionKey" ] }, { "VersionId": "cb804c1c-6d1r-4ii3-o48b-17f638469318", "VersionStages": [ "AWSPENDING" ], "LastAccessedDate": "2022-08-30T09:00:00+09:00", "CreatedDate": "2022-08-30T12:53:22.616000+09:00", "KmsKeyIds": [ "DefaultEncryptionKey" ] } ], - - - - - - - - - - - - - - - - - - - - - - - - # Delete the subject version $ aws secretsmanager update-secret-version-stage --secret-id ${secret_id} --remove-from-version-id ${version_id} --version-stage AWSPENDING --region ${region} # From the console, to make the subject secret “rotate at once” Although this has not occurred, if the database password is changed due to an issue, we execute the following command to retrieve the previous password. Since we also use alternate user rotation, it doesn't immediately disable product access to the database. We believe it won't be an issue until the next rotation is executed. $ aws secretsmanager get-secret-value --secret-id ${secret_id} --version-id ${version_id} --region ${region} --query 'SecretString' --output text | jq . For # user and password, we will set a parameter obtained by aws secretsmanager get-secret-value $ mysql --defaults-extra-file=/tmp/.$DB username for administration}.cnf -e "ALTER USER ${user} IDENTIFIED BY '${password}' # Check connection $ mysql --defaults-extra-file=/tmp/.user.cnf -e "STATUS" As for the things to do up to here, we were able prepare a foundation to achieve the following: Detect and notify the start, completion, success, or failure of a secret rotation to the relevant product teams. Ensure recovery from a failed secret rotation without affecting the product. Our battle did not stop here Although we could prepare the major functions as described, we identified three additional tasks that we needed to address. Execute secret rotation within the company’s defined governance limits. Align rotation timing with the schedule set by users registered in the same DB Cluster. Monitor compliance with the company’s governance standards. In order to achieve them, we had to develop peripheral functions. To build a mechanism to monitor the degree of compliance has been observed for the standard of the governance constrains defined by the company What we should do in this is, in a nutshell, to obtain lists of all users existing in every DB Cluster, and to check if dates of password updating for every user should be within a duration required by corporate governance. We can obtain the latest password updating date of every user after logging in each DB Cluster and executing the following query. mysql> SELECT User, password_last_changed FROM mysql.user; +----------------+-----------------------+ | User | password_last_changed | +----------------+-----------------------+ | rot_test | 2024-06-12 07:08:40 | | rot_test_clone | 2024-07-10 07:09:10 | : : : : : : : : +----------------+-----------------------+ 10 rows in set (0.00 sec) This should be executed in every DB Cluster. However, we have already obtained metadata of all DB Clusters every day and automatically generated Entity Relationship Diagram and my.cnf, and executed a scrip to check if there is any inappropriate settings in database. We could solve this simply by adding a processing to obtain lists of users and the latest password updating dates to save them in DynamoDB. Main structure of columns of DynamoDB is as follows: DBClusterID: Partition key DBUserName: Sort key PasswordLastChanged: Latest password updating date In practice, Users automatically generated for the use of RDS but we cannot not control Users with the name of “_clone” generated by Secret Rotation function The above users should be excluded. For this reason, we obtain the really necessary data by the following query. SELECT CONCAT_WS(',', IF(RIGHT(User, 6) = '_clone', LEFT(User, LENGTH(User) - 6), User), Host, password_last_changed) FROM mysql.user WHERE User NOT IN ('AWS_COMPREHEND_ACCESS', 'AWS_LAMBDA_ACCESS', 'AWS_LOAD_S3_ACCESS', 'AWS_SAGEMAKER_ACCESS', 'AWS_SELECT_S3_ACCESS', 'AWS_BEDROCK_ACCESS', 'rds_superuser_role', 'mysql.infoschema', 'mysql.session', 'mysql.sys', 'rdsadmin', ''); In addition, we prepared a Lambda for SLI to gather information of DynamoDB. Consequently, the output is like this: ![SLI notification](/assets/blog/authors/_awache/20240812/sli.png =750x) Its output content is as follows: Total Items: The number of all users existing in all DB Clusters Secrets Exist Ratio: Ratio of SecretIDs that comply with the naming rule for Secrets Manager used in KINTO Technologies Rotation Enabled Ratio: Ratio of activated Secret Rotation functions Password Change Due Ratio: Ratio of users who comply with the corporate governance rule The important thing is to make Password Change Due Ratio 100%, There is no need to depend on Secret Rotation function as long as this ratio is 100%. With this SLI notification mechanism, we can achieve the following: Monitor compliance with the company’s governance standards. A mechanism to synchronize rotation timing with the schedule set by users registered in the same DB Cluster. We had to write two code sets to realize this mechanism. A mechanism to decide the execution time of rotation for a DBClusterID. A mechanism to set a rotation on Secrets Manager by the time determined by the above Each of these is described below. The mechanism to decide the execution time of rotation for a DBClusterID. On the assumption, execution time of Secret Rotation can be described by a schedule called rotation window . Description and the usage of rotation window can be summarized into two as follows: rate equation This is used when we want to set a rotation interval by a designated number of days cron equation This is used when we want to set a rotation interval in detail such as specific day of the week or time. We decided to use cron equation as we wanted to execute our setting in daytime of weekdays. Another point to set is “window duration” of a rotation. By combining these two, we can control the execution timing of a rotation to some extent. The relation between rotation window and window duration is as follows: Rotation window means the time when a rotation ends, not starts Window duration determines allowance for execution against the set up time by the rotation window Window duration’s default is 24 hours That means, if the rotation window is set at 10:00AM of the fourth Tuesday every month but the widow duration is not specified (24 hours), the timing for Secret Rotation will be executed sometime between 10:00AM of the fourth Monday and 10:00AM of the fourth Tuesday every month, as a case. This is hard to follow intuitively. But, if we don’t get this relationship, Secret Rotation may be executed at unexpected timing. With those assumption in mind, we determined the requirement as follows: Rotation for DB users by DBClusterID will be executed at the same timezone Window duration is for three hours Setting by too short timing may lead to see problems occurring simultaneously during a timezone from a trouble reporting to its recovery Timing of the execution is set at between 09:00 to 18:00 of weekdays Tuesdays to Fridays We don’t execute on Mondays as it is more likely that a public holiday falls on that day. As the window duration is going to be fixed as three hours, what can be set in cron equation is six hours between 12:00-18:00 Only UTC can be set in cron equation Timings of execution should be dispersed as much as possible This is because many Secret Rotations run at the same timing, restrictions of various API may be affected. And if an error of some kind may occur, many alerts will be activated and we cannot respond to them at the same time The whole flow of Lambda processing will be as follows: Data acquisition : Acquire a DBClusterID list from DynamoDB Acquire setting information of existing Secret Rotation from DynamoDB Generation of schedule Initialize all combination (slots) of week, day and hour Check if the subject DBClusterID does not exist in the setting information of existing Secret Rotation If it exists, embed DBClusterID in the same slot of setting information of existing Secret Rotation Distribute new DBClusterID to slots evenly Add new data to empty slot and if it is not empty, add data to the next slot Execute repeatedly until the last one of DBClusterID list Storing data : Data is stored after filtering setting information of the new Secret Rotation that does not duplicate with the existing data. Error handing and notification : When a serious error occurs, an error message is sent to Slack for notification. Then, DynamoDB’s column to be stored is as follows: DBClusterID: Partition key CronExpression: cron equation to set at Secret Rotation It’s a bit hard to follow, but we make a state as follows, as an image: ![Slot putting in image](/assets/blog/authors/_awache/20240812/decide.png =750x) A mechanism to decide the execution time of rotation for a DBClusterID up to here. However, this doesn’t work to set up the actual Secret Rotation. Then, we need a real mechanism to set up Secret Rotation. The mechanism to set a rotation on Secrets Manager by the time determined by the above We don’t believe that a mechanism of Secret Rotation is the only means to keep the corporate governance. More important thing is to see compliance with the governance standard defined by the company Accordingly, instead of enforcing to use this mechanism, we need a mechanism that make our users want to use it as the safest and simplest one conceived by DBRE. Perhaps, we may find such requests from the users in DBCluster, like one user wishes to use Secret Rotation, while the other use insists to manage by themselves with different method. To satisfy such requests, we will need a command line tool for setting of Secret Rotation in the unit of database user linked to DBClusterID required. We have been developing a tool called dbre-toolkit for converting our daily work to command lines as DBRE. This is a package of tools such as the one to execute Point In Time Restore easily, the one to acquire DB connecting users in Secrets Manager to create defaults-extra-file . This time, we added a subcommand here: % dbre-toolkit secrets-rotation -h 2024/08/01 20:51:12 dbre-toolkit version: 0.0.1 It is a command to set Secrets Rotation based on Secrets Rotation schedule linked to a designated Aurora Cluster. Usage: dbre-toolkit secrets-rotation [flags] Flags: -d, --DBClusterId string [Required] DBClusterId of the subject service -u, --DBUser string [Required] a subject DBUser -h, --help help for secrets-rotation It was intended to complete a setting of Secret Rotation by registering the information to Secrets Manager after acquiring a combination of DBClusterID and DBUser as designated from DynamoDB. We could achieve the following with this: Execute secret rotation within the company’s defined governance limits. Align rotation timing with the schedule set by users registered in the same DB Cluster. We completed what we had decided finally by doing all these. Conclusion Here’s what we have achieved: We developed a mechanism to detect and notify relevant product teams about the start, completion, success, or failure of a secret rotation. This involved creating a system to detect CloudTrail PUT events and notify appropriately. Ensure recovery from a failed secret rotation without affecting the product. We prepared steps to handle potential issues. We found that understanding how Secret Rotation works helps minimize the risk of fatal errors. Execute secret rotation within the company’s defined governance limits. To develop a mechanism for SLI notification. We implemented a mechanism to perform secret rotation within the company’s defined governance limits. Synchronize rotation timing with the schedule set by users registered in the same DB Cluster. We developed a mechanism to store cron expressions to DynamoDB as an equation for setting to Secret Rotation in the unit of DBClusterID. Monitor compliance with the company’s governance. To develop a mechanism for SLI notification. The whole image became like this as follows: ![The whole image](/assets/blog/authors/_awache/20240812/secrets_rotation_overview.png =750x) It is more complex that we imagined. In other words, we can say that we had thought a managed Secret Rotation too simple in a sense. The function of Secret Rotation provided by AWS is very effective if you simply use it. However, we discovered that we needed to prepare many elements in-house because the out-of-the-box solution did not fully meet our requirements. We went through numerous trials and errors to reach this point. We aim to create a corporate environment where everyone can use the KTC database seamlessly with the Secret Rotation mechanism we've developed. We also we strive to ensure that the database can be used safely and continuously. KINTO Technologies’ DBRE team is currently recruiting new team mates! We welcome casual interviews as well. If you're interested, please feel free to contact us via DM on X . In addition, we wish you to follow our corporate exclusive X account for recruitment !
アバター
Introduction Hello everyone. I am Mori from the Tech Blog Team, now the Technical Public Relations Group. Starting this April, we’ve rebranded our team as the ‘Technical Public Relations Group’ ✨ Thank you for your ongoing support‍ ️🙇‍♀️ I’ve covered my other projects in separate articles. If you’re interested, please feel free to check them out 👀 Compliance with GDPR in the Global KINTO GDPR compliance: Implementing a Cookie Consent Pop-up on a Global Website Getting Started On January 31, 2024, KINTO Technologies (KTC) held its first company-wide offline gathering as a 2024 kick off 🎉 We handled every aspect of this event bottom-up from start to finish. Here’s a behind-the-scenes look at how we put together this large-scale meeting. I’m aiming to document this for future reference, but I also hope it also serves as a helpful guide for anyone tasked with organizing an in-house event. I should have published this earlier this year, but due to my slower writing pace, it’s coming out about six months late. Sorry about that 🙇‍♀️ (I know timeliness is crucial for event management articles... 😭) Why we decided to organize the event During the COVID-19 pandemic, the number of employees increased dramatically, and we now have about 350 employees. At this scale, it is difficult to create a sense of connection and unity, and there were more calls for offline and team-building events than before. In addition, since there are not many opportunities to send messages from top management, it took time for the overall vision to spread. Given these challenges, three team members who frequently handle event management started planning, noting, ‘Since it’s post-COVID-19, an opportunity for all employees to gather might help improve these issues a bit.’ This was early November last year. First, the outline Since it was decided to be held in January, there was only three months for us three until the implementation, so the schedule was quite tight. We decided to create a rough schedule as follows: First of all, in order to gain approval for holding the event itself, we considered the outline of the project as below: Event Purpose 2023 recap and kick off 2024. Share the company-wide vision and encourage cross-departmental communication to foster unity within the organization. Event Agenda Expanded version of the monthly All-Employee Meeting (Development Organization Headquarters All-Hands) Online participation possible for the first half (within work hours) Offline-only social gathering (outside of work hours) ** Content ** | Category | Time | Contents | Note | | ---- | ---- | --- | ---- | | --- | | | Rehearsal | 15:00-16:00 | Venue Setup/Rehearsal | Sound preparation and coordination, etc | | | 16:00 ​-16:30 Admission to Reception process ​ | Attendee Reception | | Main Part | 16:30-16:35 ​ | Opening | | | | 16:35 ​-16:40 ​ ​ | Looking Back on 2023(Vice President) | Review of 2023 and the Outlook for 2024 | | | 16:40-17:30 ​ ​ | 2023 Kanji of the Year| Was also held at the end of 2022. Review of each group | | | | 17:30 ​-17:40 ​ | Break/Preparing for Presentations​​ | | | | | 17:40-18:35 ​ K-1 Grand Prix ​​​| Each division presents their 2023 highlight project and awards will be given to the best presentations! | | | | 18:35 ​-18:45 ​ | ​Break​​​​ | | | | | 18:45-19:00| K-1 Grand Prix Result Announcement ​​​​​| Awards and Comments from the Winners | | | | 19:00 ​-19:05 ​ | Summary(President) | 2023 Summary and prospects for 2024 | | Reception | 19:05-19:20 | Take Pictures/ Break/ Room Layout Change | | | | | 19:20 ​-20:50 ​ | Social Gathering​| Toast & Kagami biraki (ceremonial opening of a sake barrel) ・ Company-wide interaction, mini games included! ​ | | | 20:50-21:00| Cleanup​​​ ​ | 21:00 Leave | Engage Every Team! Since the outline had been decided, we announced it internally in order to get an idea of total number of employees who would be willing to attend. Typically, in-house events are announced to the entire company via Slack, but this is a company-wide event. As such, it requires collaboration from every group to succeed️🤦‍♀️ So, we asked each group to designate a person to coordinate the rest of the teams. Having one contact person per group allowed us to gather responses more efficiently, avoiding the need for repeated announcements from management. This approach enabled us to collect responses smoothly and within the deadline. Thank you very much to everyone in charge of the groups! We really appreciated your support😭❤️ ! [announce] (/assets/blog/authors/M.Mori/20240611/announce.png =500x) the announcement in my department More offline participation than expected! Since this event was set up as a Development Organization Headquarters meeting, that is, a meeting of all employees, everyone was essentially required to participate. We expected to have people who would inevitably participate online due to family reasons or business trips, but even so, we needed a venue with a capacity of 300 people. We struggled to find a venue near our office, but after a series of searches and repeated calls, we were able to miraculously book the "Kanda Square Hall" , just a 5-minute walk from our Jimbocho office. ! [Hall] (/assets/blog/authors/M.Mori/20240611/square_hall.jpg =500x) _ A very beautiful venue. Thank you, Kanda Square._ Due to the unavoidable online participation and the need for an English interpretation channel (described later), we decided to stream the All-Employee Meeting part of the event as an online webinar. Thank you very much 😭❤️ to everyone in charge of the broadcast.️ Each role performed its tasks simultaneously! When organizing an event, we typically divide the support team into smaller teams and assign tasks to each. The great thing about our KINTO Technologies is that once teams are assigned a task, they are self-directed and proactive in their approach! It was very helpful because they took initiative and openly shared their opinions. For this event, several of the previously mentioned group representatives were assigned to multiple roles. Role Task Details Overall Coordinators Colleagues in charge of overall coordination and providing advice to other groups in charge when needed. Moderators In charge of facilitating and energizing the entire event (the most crucial role!) Reception Team reviewing effective crowd management strategies, guiding attendees around the venue, and coordinating the information to be displayed throughout the event. Interpretation Coordinators Team managing communication with external English interpreters for our international team members. Kanji of the Year team Gathering all proposals from each group for a kanji representing the year 2023 to be presented at our ‘Kanji of the Year’ segment. K-1 Grand Prix team Helping gather the information and slides about the projects that will be presented per department. Summary of President and Vice President's greetings Team creating the slides of the President and Vice President messages together with the purpose of the event. Social Gathering Team coordinating the catering and what activities to do at the social gathering. Novelties Team creating the novelty items and giveaways that will be distributed to everyone. Moderators This time, we enlisted three experienced moderators to be our presenters and energize the event. I’ll share more details about how the event went in a future article. According to the agenda, they divided and assigned themselves the segments they would cover, determined the slides needed for each segment, and planned how to engage and get everyone excited. Although we only had a rough schedule, they identified key concerns for moderating, created their own scripts, and more. They took on many tasks independently, even though we didn’t explicitly ask for them. I was truly impressed 😭❤️ ! [shinko] (/assets/blog/authors/M.Mori/20240611/shikai_shinko.png =500x) _ List of points of concern _ ! [Script] (/assets/blog/authors/M.Mori/20240611/shikai_script.png = 500x) _Moderator’s script _ Reception Even though it’s an internal event, efficient reception is crucial when dealing with such a large number of people. Five team members volunteered as the main receptionists, (and even more assisted us on the day of the event!!) At a reception, smooth guidance is key! I believe it determines the first impression of an event. The longer attendees have to wait to be checked in, the more frustrated they may become with the overall experience. In this case, we improved the process by involving the attendees on their own check-in as much as possible, rather than just the reception staff marking 〇 or X manually or handing out the event swags. We implemented the following flow for its process: By creating in advance a layout with different tables to ensure a quick flow, we were able to guide them to the venue very smoothly without creating crowds of people blocking the reception and entrance. However, we regretted that the guidance to arrive to the venue was not very thorough. We've taken notes to improve that the next time📝 Interpretation Coordinators KTC has a lot of international team members, many of whom are more proficient in English than Japanese. Since this event would include key topics from management, we decided to have interpreters for the main segments. Performing simultaneous interpreting for two and a half hours worth of content is far beyond what an amateur could manage 🤦‍♀️ So we decided to enlist the help of a professional interpretation company that has long supported our orientations for this event as well. 🔻By enabling the Language Interpretation feature on Zoom, attendees can switch audio channels to hear the translated audio or the original version at will🔻 The interpreter listens to Japanese 👂 and speaks simultaneously in English 🗣️ on the English channel, so that the English channel broadcasts the English audio. You can learn how to set it up here 👉 Language Interpretation in meetings or webinars The coordinators maintained constant communication with the off-site interpreters via a separate channel to ensure there were no audio or video issues. Thanks to the interpreters, the management’s message was accurately conveyed to everyone. I can’t speak highly enough of professional interpreters and their skill️🙇‍♀️ 2023 Kanji of the Year We also presented this segment in 2022. Managers from each group would take the stage to present the kanji representing the year, summarize their group’s highlights, and share their outlook for the upcoming year. We asked the person in charge to compile the answers of the 22 groups in advance and reflect them in the presentation deck. Given the managers' busy schedules, we notified them in mid-December and set the deadline for January 19. ![kanji_announce](/assets/blog/authors/M.Mori/20240611/kanji_announce.png =500x) 🔻This is from the former Tech Blog Team (currently the Technical Public Relations Group). ![kanji_blog](/assets/blog/authors/M.Mori/20240611/kanji_blog.png =700x) 🔺 We asked everyone to summarized the contents of each group in Confluence and incorporate it into the materials like this!🔻[kanji_blog_ppt](/assets/blog/authors/M.Mori/20240611/kanji_blog_ppt.png =700x) It was interesting to see the colors of each group, and it was a rare opportunity to get to know what each group was doing and what they will do! K-1 Grand Prix team The highlight of this event to say the least. Every month, we give awards to outstanding projects and activities under the name of the "Kageyama Award"👉 Reference article: How We Bolstered All-Employee Meetings The purpose is to do a look back into our most highlighted initiatives, recognize the value of the work being done, and to share information across departments. For the annual award version, we decided to call it the K-1 Grand Prix. The general flow is shown in the figure below: We don’t make presentations for the monthly awards, but we did request them for this annual event. Presentation skills are also tested. Given the large number of groups, we asked each to submit a project and then selected one highlight project from each group. I participated in the Platform Division selection, and it was impressive to see a lively environment where team members from different groups could come together and praise each other . During the announcement of the qualifying rounds and throughout the event, we emphasized that the goal of the K-1GP is not to declare the best or worst but to celebrate all contributions. Of course, the underlying premise is that all the work everyone has done over the past year is excellent. The main purpose of the K-1GP was to reflect on our work and applaud each other's efforts, so at least in the qualifying rounds of the Platform Division I participated in, I was very happy to see this environment of praising each other . Projects selected in the qualifying rounds were required to prepare three-minute presentations during the week leading up to the meeting. It was a very tight schedule, and I am grateful to all the presenters🙇‍♀️‍ All the presentation materials we received were full of individuality, and I looked forward to seeing the new submissions each day. lol Greetings from the President and Vice President The messages from our President Kotera-san and Vice President Kageyama-san were also key parts of our agenda. This segment was very important because we rarely have opportunities to hear directly from them in our monthly general meetings. Specifically from Kotera-san, whom we typically only hear from at KINTO/KTC joint meetings. I believe that hearing a clear message from upper management helps align everyone’s efforts toward a common goal. Like providing a solid foundation. The organizing team discussed in advance our vision for KTC engineers, our goals for KTC in 2024, and what we wanted to hear from upper management. We then summarized these points into an overall structure for their review and revision. To make the slides easier to convey, our designers from the Creative Office helped us. They choose words that would not be misunderstood by our international members and complemented them with visuals. ![President_message](/assets/blog/authors/M.Mori/20240611/president_message.jpg =500x) _ Visualizing the President’s Message _ This time, Toyota's new vision (Inventing our path forward together) was announced in a timely manner, and was also shared again by the President. ![toyota_message](/assets/blog/authors/M.Mori/20240611/toyota_message.jpg =500x) Inventing our path forward together Social Gathering The best part of offline events is the opportunity for socializing. This time, we were able to customize the hamburgers with our logo through our catering services, making them look very upscale ✨ ![Logo_burger](/assets/blog/authors/M.Mori/20240611/logo_burger.jpg = 500x) Catering was set up in the foyer, and nothing was placed in the main venue, which made it a bit inconvenient to walk back and forth for food and drinks. We made a toast during the ‘kagami biraki’ ceremony. Since it was the first time for all of us organizers to prepare a kagami biraki, we did a lot of research and became a bit anxious upon reading that we needed a crowbar and a large cutter. However, we discovered a very convenient and unique barrel on the KURAND [^1] website that didn’t require any special tools, so we decided to order that one [^1]: Later, we collaborated with KURAND and cosponsored the event "Source Code Review" Festival organized by our company. ! [kagamibiraki] (/assets/blog/authors/M.Mori/20240611/kagamibiraki.jpg = 500x) Isn't it so cute?? This design was also created by of our Creative Office 💯 After the toast, everyone was basically free to eat and drink, but we are talking about 260 people. As organizers, our goal was make this an opportunity for people who don’t usually interact to engage in conversation. We looked at what we could do to get the conversations started. Should we start with playing games in teams? We were worried that there were too many people, and we didn't want to force participation... On our search for a good method to spark conversations, we discovered Rally : a service that helps you create stamp rallies easily with your smartphone. Rally can scan QR codes to mark virtual stamps in its app. By distributing QR codes by department and encouraging everyone to collect stamps from all departments, we could facilitate interaction among participants! It was a quick decision. We could customize the design extensively even with the free plan, and we were able to have it ready in a week. 🔻Our instructions to use Rally ![rally_slides](/assets/blog/authors/M.Mori/20240611/rally_slides.jpg = 700x) We affixed QR code stickers to the ID cases distributed per departments at the reception, allowing people to scan them and collect the virtual stamps on their smartphones. It was excellent in terms of ease of preparation and as a tool for communication. Overall, it worked very well. It was moving to see people from different departments talking to each other smoothly without feeling forced. ![rally_poster](/assets/blog/authors/M.Mori/20240611/rally_poster.jpg =500x) _ Poster displayed on the day_ Novelties Another important preparation not to be forgotten is the novelty items. Due to the tight schedule, we couldn’t find what we needed at first, and the Creative Office made a lot of things for us. K-1 GP logo Certificate of Recognition ![idcase](/assets/blog/authors/M.Mori/20240611/design_k1_logo.png =300x) ![award](/assets/blog/authors/M.Mori/20240611/design_award.jpg =300x) Slide Master Barrel design for the Kagami biraki ![slidemaster](/assets/blog/authors/M.Mori/20240611/slide_master.jpg =300x) ![sakadaru](/assets/blog/authors/M.Mori/20240611/design_sakadaru.png =300x) ID card case (distributed to everyone) Staff T-shirt ![idcase](/assets/blog/authors/M.Mori/20240611/design_idcase.jpg =300x) ![staff_shirts](/assets/blog/authors/M.Mori/20240611/design_staff_t.jpg =300x) Tumbler (stamp rally giveaway, for those who collected all stamps) Tote bag (stamp rally giveaway, for those who collected all stamps) ![tumbler](/assets/blog/authors/M.Mori/20240611/design_tumbler.jpg =300x) ![eco_bag](/assets/blog/authors/M.Mori/20240611/design_bag.jpg =300x) Even when I look at it again now I want to say “How much more are we making them make stuff, poor guys!” haha Additionally, our in-house engineers developed a tool to automate the creation of everyone’s name tags. 🔻The Slack icon, department, name, and KTC logo printed for everyone. ![Name_card](/assets/blog/authors/M.Mori/20240611/namecard.jpg = 300x) I casually mentioned, ‘It would be nice to have something like that,’ and they created it right away. I’m always impressed by the speed and quality of our colleagues' work. Once again, I’d like to express my deep appreciation to everyone who helped us out, despite their other responsibilities. 🙇‍♀️🙇‍♀️🙇‍♀️ What I learned, and going forward It’s been six months now, but looking back at what I wrote, I’m still impressed by how much preparation went into it... lol Reflecting on this article, I’m reminded of the importance of clearly communicating the organization’s vision and goals to the entire company and conducting effective team-building offline. By management communicating their vision and strategy directly, we can work toward the same goal on a daily basis, based on their thoughts and directions. Additionally, successfully helping people connect with these key ideas can also boost employee motivation. By addressing this offline, the direction will be more easily absorbed, helping to build trust between employees and management. It will also help in addressing questions and concerns among employees. It has been five years since the KINTO service started, and as a company, we are in the midst to our next stage. I realized that holding such an event at this time would lead to increased engagement and fostering a sense of unity throughout our organization ✨ We can share the results of the event in other articles, but there were many positive responses from participants, such as ‘I feel more motivated to work’, ‘I gained a better understanding of what other teams are doing’, or ‘I learned more about upper management’s perspectives’ 😄 We aim to make this event an annual tradition, using what we’ve learned from this year's operation to make further improvements for the next one💪 And here I am, having written almost 7,000 words before I knew it. That’s how you can see how attached I was to this event. Thank you for reading until the end! KINTO Technologies is planning a variety of events, both internally and externally, in the future! We are always posting our external events at Connpass , so please join us if you are interested 😄
アバター
Hello! I'm an organizational development coordinator in the Human Resources Group. After joining the company in January 2023, the first company-wide event I organized turned out to be a heartwarming experience, so I decided to write about it. This article covers an event held in February 2023. KTC #thanks Days ![KTC #thanks Days](/assets/blog/authors/hr-team/thanks-days/thanks-days.png =400x) This is the name of the event. Within the company, we refer to KINTO Technologies as KTC. ■Event Overview ・Date: February 13 - February 15, 2023 ・Locations: Muromachi Office, Jimbocho Office, Nagoya Office and Osaka Tech Lab ・Details: At each location, we set up a “Free Snack Bar” where employees could fill a cup with their favorite snacks to give as a gift. They attached a thank you card with a note of appreciation, and exchanged these snacks with their colleagues. Here’s what the filled snack cups looked like So cute... Why we held this event We had two main reasons: to enhance communication and to build a company culture of mutual appreciation. After joining the company, I spoke with various colleagues and discovered that many wanted better cross-team communication across different positions, roles, ages, and genders. We hoped that this event would be an opportunity for everyone to share the gratitude they hadn’t been able to express or had forgotten to convey on a daily basis. Our aim was to deepen communication and pave the way for further interactions. ●Why we focused on gratitude When I researched what kind of communication measures to use, I found that expressing gratitude to one another has a significant impact. ・It creates feelings of gratitude, kindness, and interest in others ・It makes you focus on the strengths and positive aspects of others ・It stimulates communication ・It also increases productivity (some data shows that happiness increases productivity by 12%, while unhappiness reduces productivity by 10%) ・It releases oxytocin, the happiness hormone, making you feel happy and more. We found that communication through expressing gratitude leads to higher quality conversations than without. ●The existence of the #thanks channel In addition, KTC had a wonderful Slack channel called "#thanks" that spontaneously emerged, with various "thanks" messages posted every day. However, only about 10% of employees were contributing, so we hoped to use KTC #thanks Days as an opportunity to increase channel usage. We aimed to use this event as a catalyst for creating a culture of daily gratitude. The actual event The Snack Bar was a huge success, with many people gathering every day! Each location ended up restocking snacks three times, which was a delightful outcome! It was impressive to see how much fun everyone was having while choosing their snacks. As for the Slack channel... Among the many cute posts, there was also this heartwarming one... ...? ? ? Someone posted "Ri-Ga-To-U"(“Thank you” in Japanese) to the #thanks channel and handed the remaining letter "A" to the graduating members as a farewell gift. What a wonderful gesture! It was truly a memorable and touching moment! Channel Promotion Results Did the number of users actually increase? #thanks subscribers: increased by 117% #thanks posts: 119 new posts Total reactions: over 2,000 Number of contributors: increased by 332% (from 19 in January to 63 in just three days) These were also impressive results!! Lastly As a result of implementing this event across all locations, we received a significant number of reactions on Slack and saw that many people enjoyed the three days. Additionally, since about 25% of KTC’s employees are from overseas, we realized that gratitude transcends language barriers. Even if someone couldn’t read the words, the feelings of appreciation were clearly communicated. Although there were some words I couldn't catch, there were many instances where I could tell I was being appreciated. Each time, I mentally translated it to "Thank you for the amazing initiative!"which greatly boosted my self-esteem. <In fact, the POP display had "Thank you" written in all the languages of our employees’ countries.> This experience made me realize how fundamentally important gratitude is, and how transforming it into words and actions can be so profoundly impactful. Gratitude transcends borders. By being receptive to the actions of others, feeling a sense of appreciation, and expressing it, we aim to make these practices a natural part of our company culture. I am committed to helping build this habits at KTC and strengthening our organization. Thank you so much for allowing me to take on such a wonderful project shortly after joining the company. 감사합니다!
アバター
Introduction Hello, I'm Tada from the SCoE Group at KINTO Technologies (from now on referred to as, KTC). The term SCoE, which stands for Security Center of Excellence, might still be unfamiliar to some. At KTC, we reorganized our CCoE team into the SCoE Group this past April. In this blog, I would like to share the background and mission behind our new SCoE organization. For more information on the activities of our CCoE team, please refer to the previous articles if you are interested. Background and Challenges To explain how the SCoE group was founded, it is important to first understand its predecessor, the CCoE team. The CCoE team was established in September 2022. Since I joined KTC in July 2022, so it was formed shortly after I started. At the time of its establishment, our CCoE had two main objectives: Using cloud technology Ensuring continuous efficient development through common services, templates, knowledge sharing, and human resource development. Regulating the use of cloud services Allowing the use of cloud resources with proper policies to maintain a secure state at all times. The CCoE team engaged in various activities based on these two dimensions: Utilization and Regulation. However, since other teams within the same group had already been central to cloud utilization before the inception of the CCoE team, the CCoE's main focus shifted primarily to Governance. Regarding the Regulation aspect, as mentioned in a [previous article](https://blog.kinto-technologies.com/posts/2023-06-22-whats-ccoe-and-security-preset-gcp/), we mainly carried out the following activities: Creating standardized cloud security guidelines Providing pre-configured secure cloud environments Conducting cloud security monitoring and improvement activities Particularly in the area of monitoring and improvement activities, the team checked for deficiencies in the cloud environments used and configured by the product side, identified risky settings and operations, and, if any issues were found, requested and supported the product teams in implementing improvements. However, each product organization had a different approach to security and the level of awareness of it differed, so in some cases security was given a low priority and improvements did not progress. On the other hand, looking across KTC, there were multiple organizations covering the security aspect of each area. In addition to the organizations covering the security of back-office and production environments, there were three separate entities, including the CCoE team, covering cloud security. SOC operations were also conducted independently by each organization, which caused delays in forming company-wide security measures and made it difficult for product teams to identify the correct point of contact for security-related inquiries. At a company-wide level, the Security Group, which covered the security of product environments, played a central role. The CCoE team acted as a bridge between the Security Group and the product teams, carrying out the cloud security monitoring and improvement activities. Establishment of the SCoE Group The SCoE Group was established in response to the context described above to address the following challenges: To promote cloud security improvement activities To unify security-related organizations within KTC When it comes to the second point, consolidating three separate entities into a single department (the IT/IS Division) has enabled more efficient and rapid operations. As for the first point, the promotion of cloud security improvement activities, it was taken within the IT/IS Division as well along with the security topics, strengthening the company’s overall approach to security efforts. Previously, CCoE activities were conducted as one team within the Platform Group. However, now that the department’s name included the word Security, our commitment to it has increased. The change from Cloud CoE to Security CoE not only enhanced our focus on cloud security but also strengthened the organization's security functions and emphasized our dedication to cloud security. Being part of the same division as the Security Group allows us to implement security improvement activities more quickly. While there was some regret about the CCoE's dissolution after a year and a half, we accepted the change because the CCoE's main focus was on governance. Although the formal organization has been dissolved, the activities of CCoE continue as a virtual organization across the entire company. SCoE Group’s Mission With the establishment of the SCoE Group, the mission has been defined as follows: To implement monitoring guardrails and take corrective actions in real time The term “guardrails” here refers not only to preventive or detective measures but also to configurations and attacks that pose security risks. Given the current state of cloud security, many incidents occur due to cloud configuration issues, and the time between identifying a posture flaw and experiencing an actual incident is rapidly decreasing. Therefore, we believe that the mission of SCoE is to quickly respond to security risks as they arise and to ensure we are well-prepared in advance to handle such situations effectively. Specific activities of the SCoE Group To achieve our mission, the SCoE Group undertakes the following activities: Prevent security risks Continuously monitor and analyze security risks Respond swiftly to security risks To prevent security risks, we continue to create standardized cloud security guidelines and providing pre-configured secure cloud environments, a practice carried over from our CCoE days. While our focus has primarily been on AWS, we are now expanding our efforts to include Google Cloud and Azure. To ensure these practices are well integrated within the company, we also conduct regular training sessions and workshops. In terms of "Continuously monitor and analyze security risks," we have primarily focused on CSPM (Cloud Security Posture Management) and SOC. However, we are now expanding our activities to include CWPP (Cloud Workload Protection Platform) and CIEM (Cloud Infrastructure Entitlement Management). Additionally, we have started the process of consolidating SOC operations, which were previously conducted separately by three different organizations, into a single unified operation. In terms of what we do to respond swiftly to security risks, we have started exploring the automation of configurations, scripting, and the use of generative AI. We believe that in the future, it will be difficult to maintain a secure environment in the field of cloud security without utilizing generative AI, and we are actively considering its use. Summary At KINTO Technologies, we have restructured the CCoE team into the SCoE Group. This restructuring aims to enhance our focus on cloud security in a more specialized manner by continuing the Regulation activities previously undertaken by the CCoE. Moving forward, the SCoE Group will play a key role in leading the evolution of our cloud security. As cloud technology advances and cloud security becomes increasingly complex, we aim to minimize its security risks and ensure the delivery of safe and reliable services. We are committed to providing the essential support needed to achieve this. Thank you for reading until the end. Closing words The SCoE Group is looking for new team members to work with us. Whether you have practical experience in cloud security or are simply interested and eager to learn, we encourage you to get in touch. Please feel free to contact us. For more details, please check here
アバター
はじめに 初めまして。KINTO ONE開発部の新車サブスク開発グループでフロントエンド開発を担当しているITOYUです。 今、Webアプリケーションを作成する際はVue.js、React、Angularなどのフレームワークを使うことが一般的です。新車サブスク開発GでもReact、Next.jsを使って開発を行っています。 やれReactのver.19がリリースされた、やれNext.jsのVer.15がリリースされたというように、ライブラリやフレームワークのバージョンアップが頻繁に行われています。そのたびに更新された機能や変更点のキャッチアップを行い、知識をアップデートする必要があります。 そして昨今のフロントエンドの進化は目覚ましいものがあります。数ヶ月前まで使っていたライブラリやフレームワークが、数ヶ月後には旧式となり、新しいライブラリやフレームワークが登場することも珍しくありません。 このような状況下で、フロントエンド開発者は常に新しい技術やライブラリ、フレームワークに対してアンテナを張り、情報収集を行い、学習を続ける必要があります。 これはフロントエンド開発者の定めであり、フロントエンド開発者にとっての楽しみでもあります。 熱い情熱と飽くなき好奇心を持つフロントエンド開発者は、新しい技術やライブラリ、フレームワークを学び使いこなすことで、自分のスキルを向上させ、より良いWebアプリケーションを効率的に開発しベストプラクティスを追求し、 フロントエンドの達人 を目指しています。 しかしフロントエンドにおけるライブラリやフレームワークの根底にはJavaScriptがあります。果たして私たちはJavaScriptを100%理解し、使いこなしているのでしょうか。 JavaScriptの機能を使いこなせていないのに、ライブラリやフレームワークを使いこなすことができるのでしょうか。 フロントエンドの達人と呼べるのでしょうか。 かくいう私もその問いかけに対して、自信を持って「はい」と答えることができません。 ということで、フロントエンドの達人を目指すべく、JavaScriptの学び直しを行い、不足している知識を補うことを決意しました。 この記事の目的 学び始めの第一歩として、JavaScriptの基本的な概念である スコープ について学び、理解を深めることを目的としています。 あまりにも初歩すぎるだろ!と思われるかもしれません。きっと大抵のフロントエンドエンジニアの皆さんは、スコープとは何か、といちいち考えることなく、当たり前のように使いこなしていることでしょう。 ですがスコープの概念や関連する知識や名称を言語化するとなると、意外と難しいものです。 この記事では、スコープの概念を理解するために、スコープの種類について理解を深めることを目的としています。 この記事を読み終わった後に、新しい実装方法が身に付くといったことは無いでしょう。ですが、スコープの概念を理解することで、JavaScriptの挙動を理解し、より良いコードを書くための基礎を築くことができるでしょう。 :::message この記事で記載されているJavaScriptのコードや概念は、ブラウザ上での動作を前提として解説しています。 Node.jsなどの環境によっては、挙動が異なる場合がありますので、ご注意ください。 ::: スコープ JavaScriptではスコープという概念があります。スコープとは 実行中のコードから参照できる変数や関数の範囲 のことです。 まずは以下のスコープの種類について見ていきましょう。 グローバルスコープ(global scope) 関数スコープ(function scope) ブロックスコープ(block scope) モジュールスコープ(module scope) グローバルスコープ グローバルスコープとは、プログラムのどこからでも参照できるスコープのことです。 変数や関数にグローバルスコープを持たせる方法は大まかに以下の通りです。 グローバルオブジェクトのプロパティに追加された変数 スクリプトスコープを持つ変数 グローバルオブジェクトのプロパティに追加された変数 グローバルオブジェクトのプロパティに変数や関数を追加することで、グローバルスコープを持たせることができます。 環境によってグローバルオブジェクトは異なりますが、ブラウザ環境ではwindowオブジェクト、Node.js環境ではglobalオブジェクトがグローバルオブジェクトになります。 今回の例ではブラウザ環境を想定して、windowオブジェクトにプロパティを追加する方法を紹介します。 その方法とは、varで変数や関数を宣言することです。varで宣言された変数や関数はグローバルオブジェクトのプロパティとして追加され、どこからでも参照できるようになります。 // windowオブジェクトのプロパティに追加された変数 var name = 'KINTO'; console.log(window.name); // KINTO また、グローバルオブジェクトに追加された変数を呼ぶ際、windowオブジェクトを省略することもできます。 // windowオブジェクトを省略した変数の呼び出し var name = 'KINTO'; console.log(name); // KINTO スクリプトスコープを持つ変数 スクリプトスコープとは、JavaScriptファイルのトップレベル、もしくはscript要素のトップレベルで宣言された変数や関数が参照できるスコープのことです。 トップレベルでlet,constで宣言された変数や関数はスクリプトスコープを持ちます。 <!-- スクリプトスコープを持つ変数 --> <script> let name = 'KINTO'; const company = 'KINTOテクノロジーズ株式会社'; console.log(name); // KINTO console.log(company); // KINTOテクノロジーズ株式会社 </script> トップレベル トップレベルとは、関数やブロックの外側のことを指します。 これだけだどトップレベルの説明がわかりにくいかもしれません。以下の例でトップレベルで宣言されている変数と、そうでない変数の違いを見てみましょう。 <!-- トップレベルで宣言された変数 --> <script> let name = 'KINTO'; const company = 'KINTOテクノロジーズ株式会社'; console.log(name); // KINTO console.log(company); // KINTOテクノロジーズ株式会社 </script> <!-- トップレベルで宣言されていない変数 --> <script> const getCompany = function() { const name = 'KINTO'; console.log(name); // KINTO return name; } console.log(name); // ReferenceError: name is not defined if (true) { const company = 'KINTOテクノロジーズ株式会社'; console.log(company); // KINTOテクノロジーズ株式会社 } console.log(company); // ReferenceError: company is not defined </script> 上記のコードだと、 getCompany 関数内で宣言された name 変数と、 if 文内で宣言された company 変数は、関数の中やif文のブロックの中でのみ参照できます。 グローバルオブジェクトとスクリプトスコープの違い トップレベルでlet,constで宣言された変数は、varで宣言された変数と同様にグローバルスコープを持ち、どこからでも参照できるようになります。 しかし、let,constで宣言された変数はvarで宣言された変数と異なり、グローバルオブジェクトのプロパティには追加されません。 // let,constで宣言された変数はグローバルオブジェクトのプロパティには追加されない let name = 'KINTO'; const company = 'KINTOテクノロジーズ株式会社'; console.log(window.name); // undefined console.log(window.company); // undefined :::message グローバルオブジェクトの扱いは慎重に varを使ってグローバルオブジェクトのプロパティに変数や関数を追加する方法は、グローバルオブジェクトの汚染を招くため、避けるべきです。 その理由として、異なるスクリプト間で変数や関数の名前が重複すると、予期せぬ挙動を引き起こす可能性があるためです。 なのでグローバルスコープを持たせたい場合は、let,constで宣言された変数を使うことが推奨されています。 ::: 関数スコープ 先ほどのスクリプトスコープを持たない変数の例の中で登場しましたが、関数に囲まれた波括弧{}内で宣言された変数や関数は、その関数内でのみ参照出来ます。これを 関数スコープ といいます。 const getCompany = function() { const name = 'KINTO'; console.log(name); // KINTO return name; } console.log(name); // ReferenceError: name is not defined name変数は関数の中で宣言されているため、getCompany関数の中でのみ参照できます。なので関数の外からname変数を参照しようとするとエラーが発生します。 ブロックスコープ こちらも先ほどのスクリプトスコープを持たない変数の例の中で登場しましたが、波括弧{}で囲まれた範囲内で宣言された変数や関数は、そのブロック内でのみ参照できます。これを ブロックスコープ といいます。 if (true) { let name = 'KINTO'; const company = 'KINTOテクノロジーズ株式会社'; console.log(name); // KINTO console.log(company); // KINTOテクノロジーズ株式会社 } console.log(name); // ReferenceError: name is not defined console.log(company); // ReferenceError: company is not defined このようにletとconstで宣言された変数はブロックスコープになり、波括弧{}内で宣言された変数は波括弧{}内でのみ参照できます。 :::message 関数宣言とブロックスコープ 関数宣言をブロック内で行うと、関数宣言はブロックスコープを持たないため、関数はスコープ外からも参照できます。 ※JavaScriptのバージョンや実行環境によって結果が異なる場合があります。 if (true) { function greet() { console.log('Hello, KINTO'); } greet(); // Hello, KINTO } greet(); // Hello, KINTO なので関数に対してブロックスコープを持たせたい場合は、ブロックスコープを持つ変数宣言を利用して関数を代入する方法を使うことが推奨されています。 if (true) { const greet = function() { console.log('Hello, KINTO'); } greet(); // Hello, KINTO } greet(); // ReferenceError: greet is not defined ::: モジュールスコープ モジュールスコープとは、モジュール内で宣言された変数や関数が参照できるスコープのことです。これにより、モジュール内の変数や関数は、そのモジュール内でのみアクセス可能となり、外部からは直接参照することができません。 モジュール内で宣言された変数や関数を外部から参照するためには、 export を使って外部に公開し、 import を使ってその変数や関数を利用するファイルに取り込む必要があります。 例えば、 module.js というファイルに以下のように変数を宣言します。 // module.js export const name = 'KINTO'; export const company = 'KINTOテクノロジーズ株式会社'; const category = 'サブスクリプションサービス'; // この変数はexportされていないため、外部からは参照できません。 exportされた変数は、別のファイルでimportすることで参照することができます。 // モジュールスコープを持つ変数の呼び出し import { name, company } from './module.js'; console.log(name); // 出力: KINTO console.log(company); // 出力: KINTOテクノロジーズ株式会社 // `category`はexportされていないため、この行はエラーを引き起こします。 console.log(category); // ReferenceError: category is not defined exportされていない変数は、外部から参照しようとするとエラーが発生します。これは、モジュールスコープがその変数を外部から隠蔽しているためです。 // モジュールスコープを持たない変数の呼び出し import { category } from './module.js'; // SyntaxError: The requested module './module.js' does not provide an export named 'category' console.log(category); // importが失敗するため、この行は実行されません。 このように、モジュールスコープを理解することは、JavaScriptでのモジュール間の依存関係を管理する上で非常に重要です。 まとめ スコープとは実行中のコードから参照できる変数や関数の範囲のこと グローバルスコープとは、どこからでも参照できるスコープのこと スクリプトスコープとは、JavaScriptファイルのトップレベル、もしくはscript要素のトップレベルで宣言された変数や関数が参照できるスコープのこと 関数スコープとは、関数に囲まれた波括弧{}内で宣言された変数や関数が参照できるスコープのこと ブロックスコープとは、波括弧{}で囲まれた範囲内で宣言された変数や関数が参照できるスコープのこと モジュールスコープとは、モジュール内でのみ参照できるスコープのこと 今回はJavaScriptにおけるスコープの種類について学びました。次回はスコープに関連する知識について紹介します。
アバター
Introduction Hello. I am Nakaguchi from KINTO Technologies, Mobile App Development Group. I work on developing KINTO Easy Application App and also organize study sessions and events for the iOS team. Eight members of our iOS team attended try! Swift Tokyo 2024 , which was held from March 22 to 24, 2024. Later, as part of our study sessions, we held LTs (lightning talks) to reflect on our experiences. Out of the eight participants, five gave presentations through LT, while the remaining three published articles on the KTC Tech Blog. Here are their blog posts: Recap of Try! Swift Tokyo 2024 Trying! Swift Community in 2024 One more article will be published soon!! LT event details Usually, our team study sessions are conducted solely within the iOS team, but today we had guests including members from the "Manabi no Michi no Eki (roadside station for learning)", (more information here) , who support company-wide study sessions, as well as members from the Android team. With over 20 participants, it was a very lively event. Here is the online venue! Everyone has a lovely smile! 😀 Here's the offline venue! Due to the rain or possibly hay fever, many people were working from home that day, so the turnout was a bit low. However, everyone who came was smiling happily! 😀 Additionally, we set up a dedicated thread on Slack during the iOS team’s study session, and everyone enthusiastically engaged in the discussion. It was a huge success, with over 150 comments in just one hour! First speaker: Mori-san Mori-san shared a wide range of impressions about the sessions they attended! It was also memorable that Mori-san expressed gratitude to the event staff and simultaneous interpreters. I got the impression that Mori-san already has a deep understanding of SwiftUI and TCA, which are used in their work. This year's try! Swift had many sessions that delved deeper into the basics, which likely helped deepen their knowledge. Here is a video of Mori-san's presentation! 2nd speaker: Hinomori-san ( ヒロヤ@お腹すいた ) Hinomori-san was involved as a staff member for three days and shared many behind-the-scenes stories with us! You can also check out his blog article here ! It turns out that much of the setup around were actually done by Hinomori-san. Over the three days, I saw Hinomori-san working as a staff member many times and seemed to be very busy. The scene during the closing on the second day, where all the organizers, speakers, and staff gathered on stage, was very moving, and Hinomori-san stood out among them. Here is Hinomori-san's presentation! 3rd speaker: Nakaguchi This will be my LT. This year, I want to focus on catching up with visionOS, so in my LT, I also talked about “Creating a visionOS app with Swift” (Day 1) and “How to start developing spatial apps unique to Apple Vision Pro” (Day 3). I haven’t had the chance to develop for visionOS in my work or private projects yet (and of course, I don’t have the actual device), but my desire to work with visionOS has increased tremendously! Here is my presentation! 4th speaker: Ryomm-san Ryomm-san had already released a participation report on Zenn, and it was presented during the LT. (Released on 23 rd March 👀...so fast!!) I participated in try! Swift Tokyo 2024! https://zenn.dev/ryomm/articles/e1683c1769e259 Ryomm-san provided an overall recap of the sessions, as well as reflections on the sponsor booths and the after-party. With amazing communication skills, Ryomm-san exchanged information with many people, including the speakers! According to Ryomm-san, the trick to starting a conversation with the person next to you is courage and a friendly "Hey there!"!! In these kinds of events, probably everyone wants to talk to someone, so don’t hesitate to strike up a conversation. We should all follow Ryomm-san’s example 😭 Here is Ryomm-san's presentation! 5th speaker: Goseo-san Goseo-san shared their impressions of the session “How to build a sense for designing good applications” (Day 1)! They actually tried out the source code which was introduced during the session and shared their thoughts on implementing it with SwiftUI. It was enlightening to learn that animations in SwiftUI still have some quirks. Here is Goseo-san's presentation (held on a different day, later) Conclusion It was my first time participating try! Swift, which was held for the first time in five years. I usually only attend conferences online, so this was my first offline experience. It was incredibly educational and a valuable experience. In the future, I would like to get more involved by participating as a sponsor or staff member. I believe it was a great initiative for our team to turn our participation in try! Swift into tangible outputs, such as LT events and blog writing. I hope we can continue these kinds of activities at future large conferences like try! Swift and iOSDC.
アバター
Introduction Hello! I am TKG from the Corporate IT Group at KINTO Technologies (KTC). As a corporate engineer, I usually manage the Service Desk and Onboarding Operations. The other day, I presented the "Study Session in the Format of Case Presentations + Roundtable Discussions, Specialized in the Corporate IT Domain" at the event **“KINTO Technologies MeetUp! 4 case studies for information systems shared by information systems” ** " This time, I would like to introduce the content of the case study presentation from that study session, along with some additional information! First, the presentation materials: The presentation materials are stored on Speaker Deck. The story of how the help desks of KINTO and KINTO Technologies have collaborated (and are continuing to collaborate) - Speaker Deck Choosing the theme Currently, I hold positions in both KTC and KINTO, and I am in charge of the help desk area in both companies. When I was thinking about what to present, I realized that there aren’t many case studies on how close companies collaborate with each other. So, I chose this as my theme. To be honest, I had some doubts about whether it was worth presenting since it wasn’t something particularly "flashy”. However, I motivated myself by thinking that these not particularly glamorous topics are exactly the ones that should be shared, and I went ahead to prepare the content. About KINTO and KTC As this story is about the collaboration between KINTO and KTC, I thought it was important to first explain the relationship between the two. I have always found it to be quite unclear, both before and after I joined, so I would like to explain their relationship from my point of view. They are sibling companies rather than subsidiaries, and there's a common misconception that KTC only develops for KINTO. In reality, we also develop for our parent company, Toyota Financial Services (TFS), and create apps for end users, such as my route and Prism Japan. The IT environments of the two companies are quite different. You can see in the simplified chaos map above that KINTO appears to be fully cloud-based, but its core systems operate on-premises within the internal network. On the other hand, KTC does not have an internal network at all. Each office operates independently. Our Muromachi Office has bases on the 7th and 16th floors, but each operates independently. The only on-premises equipment consists of the network devices and multifunction devices at each location. This is the structure of the IT departments of both companies. While KINTO is divided into two sections, Service Desk (Help Desk) and Infrastructure Management (IT Support), KTC is divided into five. What I will be discussing today is the Service Desk at KINTO that I am in charge of, what it would be the "Tech Service" at KTC. Both departments handle help desk operations. The roles of the various organizations within KTC are extensive enough to require multiple articles, so I will omit them here. This concludes the explanation of the relationship between KINTO and KTC. Episode 1. The story of implementing Jira Service Management (JSM) as the Inquiry Desk for both companies At KTC, we were using Jira Software (Jira) to handle inquiries. Initially, it worked well, but as the number of employees increased, issues started to arise with the existing Jira setup. The problem was that the tickets were only written in free text, which created a burden for both the submitters and the help desk. Additionally, there were instances where the help desk couldn’t check the status or handle sensitive content (since the inquiry desk’s Jira was accessible to all employees). We decided to implement a dedicated ITSM (IT Service Management) tool to allow our staff to focus on their engineering tasks (their primary responsibilities) without customizing Jira to potentially resolved these issues. Although we wanted to compare various tools, we had limited time. Given that Atlassian products were already used within the company, we chose Jira Service Management (JSM) for its compatibility. An additional advantage was that 10 licenses were available for free for one year, making it easy to test and evaluate the tool. Initially, the plan was to implement JSM only at KTC, but as we continued collaborating between KINTO and KTC, we became aware of the issues at KINTO as well, so we decided to cooperate together. The implementation started with KINTO. We first established implementation and operational experience at KINTO, following the concept of "Winning Quickly", and then leveraged that experience to roll it out at KTC. There were no major concerns during the implementation at KINTO, but several concerns arose when it came to implementing at KTC. Some of the specific concerns that emerged at that time were as follows: Q1. Won’t it require more effort if we cannot refer to other requests when issuing accounts, changing permissions, etc.? A. With JSM, it is possible to create optimized forms for each type of service request, eliminating the need to refer to other requests Q2. (Since everyone can make requests) Will service requests occur without the manager’s approval? A. While such requests may occur, the help desk will coordinated with the manager as needed Additionally, I recently had the opportunity to gather feedback from managers of various departments regarding the implementation of JSM. They said that the concerns they had previously did not turn out to be negative, and it has become much easier to track their requests. They evaluated it as a significant improvement compared to the previous system. Initially, our focus was on implementation. That was the situation. We have been continuously optimizing the inquiry forms, removing “unnecessary fields that turned out to be redundant after use”, and creating batch request forms to streamline processes. One of our top priorities has been the "expansion of the knowledge base". However, through our analysis of inquiries, we found that the proportion of service requests was higher than incident-related inquiries, which particularly require a knowledge base. This likely stems from the fact that KTC is a group of technical professionals with high IT literacy. Therefore, the focus has shifted more towards service requests, which cannot be resolved by the users themselves (i.e., only administrators can handle), rather than incident-related issues that users can resolve on their own. Currently, we are focusing on reducing the number of service requests and improving the speed of processing them. Episode 2. The story about how KINTO used KTC’s expertise to reduce costs and improve the PC replacement process (and continuing to do so) At KTC, we generally outsource the kitting process. However, since there are instances where onboarding happens suddenly, we have been working on automating the kitting process using MDM (mobile device management). At peak times, more than 20 people join in a month! For more details on this efficiency improvement, please refer to the presentation material below (in Japanese): The benefits of automating Windows Kitting - Speaker Deck On the other hand, at KINTO, we had vendors perform initial kitting from image deployment, and then installed individual applications. Although we had already been using Intune for settings, there was no specific trigger to push for further efficiency. At that time, we embarked on a large-scale PC replacement project at KINTO, which gave us the opportunity to collaborate more closely with KTC to streamline the process. By collaborating between KINTO and KTC and reviewing past documents, we were able to eliminate parts that previously required manual work and replace manual settings with Intune. As a result, we no longer needed to request vendors for image creation, achieving greater efficiency. While we have made progress in streamlining processes, we believe there is still room for improvement. Due to the different environments compared to KTC, reaching a "zero-touch" setup seems quite distant, but we would like to improve it little by little and move towards "little-touch" setup. In conclusion: Never forget to appreciate our predecessors Both KINTO and KTC have only been around for a few years since their founding, and they had to quickly establish the environments. There is no doubt that the people at that time made the best choices during the chaos of starting up, and they laid the foundations step by step. Within the changing environment, the case we discussed is an example of how we were able to successfully improve things when given the right opportunity. KINTO and KTC still have many areas that are not fully optimized, and there is a lot of room for improvement in both companies. If you are someone who is eager to take on this challenge, please join us! Together, let’s enhance the IT environments of KINTO and KTC, creating a space where staff can perform at their best without spending time on tasks other than engineering!
アバター
My name is Ryomm and I work at KINTO Technologies. I am developing the app my route (iOS). Today I will explain how to create a reference image for Snapshot Testing in any directory. Conclusion verifySnapshot(of:as:named:record:snapshotDirectory:timeout:file:testName:line:) You can specify the directory if you use this method. Background Recently, I wrote an article about introducing Snapshot Testing. However, after running it for a while, the number of test files has increased significantly, making it very difficult to find the specific test file I need. ![Large number of SnapshotTesting files](/assets/blog/authors/ryomm/2024-04-26/01-yabatanien.png =150x) Large number of Snapshot Test files So I decided to organize the Snapshot Testing files into appropriate subdirectories, but the method assertSnapshots(of:as:record:timeout:file:testName:line:) in the Snapshot Testing library pointfreeco/swift-snapshot-testing does not allow specifying the location for creating reference images. The existing directory structure related to Snapshot Testing looks as follows: App/ └── AppTests/ └── Snapshot/ ├── TestVC1.swift ├── TestVC2.swift │ └── __Snapshots__/ ├── TestVC1/ │ └── Reference.png └── TestVC2/ └── Reference.png When test files are moved to a subdirectory, the method mentioned above creates a directry __Snapshots__ within that subdirectory. Inside this directory, it creates a directory with the same name as the test file which contains the reference images. App/ └── AppTests/ └── Snapshot/ ├── TestVC1/ │ ├── TestVC1.swift │ └── __Snapshots__/ │ └── Reference.png ← Created here 😕 │ └── TestVC2/ ├── TestVC2.swift └── __Snapshots__/ └── Reference.png ← Created here 😕 As part of the existing CI system, the entire directory App/AppTests/Snapshot/__Snapshots__/ is mirrored to S3, so I do not want to change the location of the reference images. The target directory structure is as follows: App/ └── AppTests/ └── Snapshot/ ├── TestVC1/ │ └── TestVC1.swift ├── TestVC2/ │ └── TestVC2.swift │ └── __Snapshots__/ ← I want to put reference images here 😣 ├── TestVC1/ │ └── Reference.png └── TestVC2/ └── Reference.png Specify the Directory for Reference Images and Run a Snapshot Test verifySnapshot(of:as:named:record:snapshotDirectory:timeout:file:testName:line:) By using the method, you can specify the directory. The three methods provided in Snapshot Testing have the following relationships: public func assertSnapshots<Value, Format>( Matching value: @autoclosure () throws -> Value, As strategies: [String: Snapshotting<Value, Format>], record recording: Bool = false, timeout: TimeInterval = 5, file: StaticString = #file, testName: String = #function, line: UInt = #line ) { ... } ↓Execute forEach on the comparison formats passed to as strategies public func assertSnapshot<Value, Format>( Matching value: @autoclosure () throws -> Value, As snapshotting: Snapshotting<Value, Format>, Named name: String? = nil, record recording: Bool = false, timeout: TimeInterval = 5, file: StaticString = #file, testName: String = #function, line: UInt = #line ) { ... } Run the following and use the returned values to perform the test. verifySnapshot(of:as:named:record:snapshotDirectory:timeout:file:testName:line:) You can check the actual code here . In other words, as long as the same thing is done internally, it is perfectly fine to use verifySnapshot(of:as:named:record:snapshotDirectory:timeout:file:testName:line:) directly! Boom! extension XCTestCase { var precision: Float { 0.985 } func testSnapshot(vc: UIViewController, record: Bool = false, file: StaticString, function: String, line: UInt) { assert(UIDevice.current.name == "iPhone 15", "Please run the test by iPhone 15") SnapshotConfig.allCases.forEach { let failure = verifySnapshot( Matching: vc, as: .image(on: $0.viewImageConfig, precision: precision), record: record, snapshotDirectory: "Any path", file: file, testName: function + $0.rawValue, line: line) guard let message = failure else { return } XCTFail(message, file: file, line: line) } } } For our app my route , I initially passed only a single value to strategies , so I omitted the looping process with strategies . Now, although I was able to specify the directory, to follow the existing Snapshot Testing pattern, I want to create a directory based on the test file name and place the reference images inside it. The path passed to verifySnapshot(of:as:named:record:snapshotDirectory:timeout:file:testName:line:) needs to be an absolute path, and since the development environment varies among team members, it is necessary to generate the path according to each environment. Although the code turned out to be quite straightforward and cute, I implemented it as follows. extension XCTestCase { var precision: Float { 0.985 } private func getDirectoryPath(from file: StaticString) -> String { let fileUrl = URL(fileURLWithPath: "\(file)", isDirectory: false) let fileName = fileUrl.deletingPathExtension().lastPathComponent var separatedPath = fileUrl.pathComponents.dropFirst() // Here it becomes a [String]? template // Delete the path after the Snapshot folder let targetIndex = separatedPath.firstIndex(where: { $0 == "Snapshot" })! separatedPath.removeSubrange(targetIndex+1...separatedPath.count) let snapshotPath = separatedPath.joined(separator: "/") // Since we pass it as a String to verifySnapshot, I will write it as a String without converting it back to a URL. return "/\(snapshotPath)/__Snapshots__/\(fileName)" } func testSnapshot(vc: UIViewController, record: Bool = false, file: StaticString, function: String, line: UInt) { assert(UIDevice.current.name == "iPhone 15", "Please run the test by iPhone 15") SnapshotConfig.allCases.forEach { let failure = verifySnapshot( matching: vc, as: .image(on: $0.viewImageConfig, precision: precision), record: record, snapshotDirectory: getDirectoryPath(from: file), file: file, testName: function + $0.rawValue, line: line) guard let message = failure else { return } XCTFail(message, file: file, line: line) } } } This way, we can keep the reference images in their original location, while organizing the Snapshot Testing into subdirectories. This resolves the inconvenience of not being able to find the files when you want to update a Snapshot Test. There is still room for improvement, so I aim to make our development experience even more enjoyable ♪
アバター