Chief Certificate Officer

もう証明書の期限切れで頭を抱える必要はありません。「Neoflow オフィサーシリーズ Chief Certificate Officer (CCO)」

情報システム運用担当者の皆様、セキュリティベンダーの皆様。ブランドを冠した大切なドメインの証明書が、予期せぬタイミングで期限切れとなり、お客様からの信頼失墜や対応に追われる苦い経験はありませんか?

「Neoflow オフィサーシリーズ Chief Certificate Officer (CCO)」は、そのような悩みを解決するために生まれた、シンプルかつ強力な証明書管理ソリューションです。開発者自身が過去の苦い経験から、「自分の目で定期的にチェックしたい」という強い想いを形にしました。

担当者の変更、インフラの刷新、組織再編など、ビジネス環境は常に変化します。そんな変化の中で、見落とされがちなドメイン証明書の管理を、CCOが強力にサポートします。

CCOでできること

  • 簡易チェック: いつでも手軽に、専用フォーム(https://neoflow.co.jp/cco/)からドメインの証明書情報を確認できます。
  • API連携: エンドポイント向けのAPIを活用することで、証明書の有効期限チェックを自動化し、日々の運用に組み込むことができます。
  • 一括管理: 例えば、Google SpreadsheetとGoogle Apps Scriptを組み合わせることで、複数のドメインやサブドメインの証明書期限を一括で自動チェックできます。サンプルコードを参考にしてください。

CCOが選ばれる理由

  • シンプルな操作性: 複雑な設定は不要。誰でも簡単に使い始めることができます。
  • 早期発見によるリスク軽減: 期限切れ前にアラートを受け取ることで、インシデントを未然に防ぎ、ブランドイメージを守ります。
  • 運用コストの削減: 手作業での確認や、期限切れ後の対応にかかる無駄なコストを削減します。

大切なドメインを守るために。「Neoflow オフィサーシリーズ Chief Certificate Officer (CCO)」をぜひご活用ください。

function checkCertificateExpiryAndNotify() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const serverUrl = 'https://neoflow.co.jp/cco/api/';
  const emailRecipient = ''; // 通知を送るメールアドレス
  
  const data = sheet.getDataRange().getValues(); // シート全体のデータを取得
  const headers = data[0]; // 最初の行をヘッダーとして扱う
  const fqdnIndex = headers.indexOf('FQDN'); // "FQDN" 列のインデックスを取得
  
  if (fqdnIndex === -1) {
    Logger.log('FQDN 列が見つかりません。');
    return;
  }
  
  const results = [];
  let expiredCount = 0;
  let warningCount = 0;
  let successCount = 0;

  for (let i = 1; i < data.length; i++) { // データの各行をループ
    const fqdn = data[i][fqdnIndex]; // FQDN 列の値を取得
    if (!fqdn) continue; // 空白セルはスキップ

    try {
      const response = UrlFetchApp.fetch(`${serverUrl}${fqdn}/`, { muteHttpExceptions: true });
      if (response.getResponseCode() === 200) {
        const result = JSON.parse(response.getContentText());
        const expiryDate = new Date(result.expiry_date); // 有効期限を取得 (UTC)
        const now = new Date(); // 現在の日時 (UTC)
        const threeMonthsLater = new Date();
        threeMonthsLater.setMonth(now.getMonth() + 3); // 現在日時の3か月後を計算
        
        const istDate = new Date(expiryDate.getTime() + 5.5 * 60 * 60 * 1000); // IST (UTC+5:30)
        const jstDate = new Date(expiryDate.getTime() + 9 * 60 * 60 * 1000);   // JST (UTC+9:00)
        
        // フォーマット済みの日付
        const formattedUTC = expiryDate.toISOString();
        const formattedIST = `${istDate.toISOString().slice(0, 19)}+05:30`;
        const formattedJST = `${jstDate.toISOString().slice(0, 19)}+09:00`;

        // セルのスタイル変更
        const rowIndex = i + 1; // シートの行インデックス
        const utcCell = sheet.getRange(rowIndex, fqdnIndex + 2);
        const istCell = sheet.getRange(rowIndex, fqdnIndex + 3);
        const jstCell = sheet.getRange(rowIndex, fqdnIndex + 4);

        // 値を設定
        utcCell.setValue(formattedUTC);
        istCell.setValue(formattedIST);
        jstCell.setValue(formattedJST);

        // 期限に応じたスタイル設定
        if (expiryDate < now) {
          // 期限切れ: 灰色
          [utcCell, istCell, jstCell].forEach(cell => {
            cell.setFontColor('#808080'); // 灰色
            cell.setFontWeight('normal');
          });
          results.push(`[Expired] ${fqdn}: Expired - UTC=${formattedUTC}`);
          expiredCount++;
        } else if (expiryDate <= threeMonthsLater) {
          // 3か月以内に期限切れ: 赤字かつ太字
          [utcCell, istCell, jstCell].forEach(cell => {
            cell.setFontColor('#FF0000'); // 赤色
            cell.setFontWeight('bold');
          });
          results.push(`[Warning] ${fqdn}: Expires within 3 months - UTC=${formattedUTC}`);
          warningCount++;
        } else {
          // 有効期限に余裕あり: 黒字
          [utcCell, istCell, jstCell].forEach(cell => {
            cell.setFontColor('#000000'); // 黒色
            cell.setFontWeight('normal');
          });
          results.push(`[Valid] ${fqdn}: Valid - UTC=${formattedUTC}`);
          successCount++;
        }
      } else {
        results.push(`[Error] ${fqdn}: Failed to fetch certificate`);
      }
    } catch (error) {
      results.push(`[Error] ${fqdn}: Error occurred - ${error.message}`);
    }
  }

  // メール通知
  const sheetUrl = SpreadsheetApp.getActiveSpreadsheet().getUrl();
  const emailSubject = 'Certificate Expiry Check Results';
  const emailBody = `
    Certificate expiry check completed.
    
    Total FQDNs checked: ${data.length - 1}
    Expired: ${expiredCount}
    Expires within 3 months: ${warningCount}
    Valid: ${successCount}
    Errors: ${data.length - 1 - successCount - expiredCount - warningCount}
    
    Check the detailed results in the sheet: ${sheetUrl}

    Details:
    ${results.join('\n')}
  `;

  GmailApp.sendEmail(emailRecipient, emailSubject, emailBody);
  Logger.log('メール通知を送信しました。');
}