end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

ldap に登録の OpenAM 設定は、ldapsearch で export できますが、肝心な部分は暗号化

大量にある OpenAMの設定は、 ssoadm コマンドで export できます。

「OpenAMの設定は、ldap に登録されている為、ldapsearch コマンドで export できるのでは?」 と考え、試しに実行しましたが、肝心な部分は暗号化されており、NG でした。

そりゃそうですね。

以下は、試しに ldapsearch した結果の抜粋です。

$ /usr/bin/ldapsearch -x -h localhost -p 50389  \
    -D "cn=Directory Manager" -w ないしょ \
    -b "dc=openam,dc=forgerock,dc=org" \
    "*" | less

# extended LDIF
#
# LDAPv3
# base <dc=openam,dc=forgerock,dc=org> with scope subtree
# filter: (objectclass=*)
# requesting: * 
#

# openam.forgerock.org
dn: dc=openam,dc=forgerock,dc=org
objectClass: top
objectClass: domain
dc: openam

# people, openam.forgerock.org
dn: ou=people,dc=openam,dc=forgerock,dc=org
objectClass: top
objectClass: organizationalUnit
ou: People

<略>

# tokens, openam.forgerock.org
dn: ou=tokens,dc=openam,dc=forgerock,dc=org
objectClass: top
objectClass: organizationalUnit
ou: tokens

# services, openam.forgerock.org
dn: ou=services,dc=openam,dc=forgerock,dc=org
objectClass: top
objectClass: organizationalunit
objectClass: sunServiceComponent
sunKeyValue: sunidentityrepositoryservice-sunOrganizationStatus=Active
ou: services
sunxmlKeyValue: sunidentityrepositoryservice-sunOrganizationAliases=openam
sunxmlKeyValue: sunidentityrepositoryservice-sunOrganizationAliases=test-navi-
 sso13.end0tknr.com

# dashboardService, services, openam.forgerock.org
dn: ou=dashboardService,ou=services,dc=openam,dc=forgerock,dc=org
objectClass: sunService
objectClass: top
ou: dashboardService

# 1.0, dashboardService, services, openam.forgerock.org
dn: ou=1.0,ou=dashboardService,ou=services,dc=openam,dc=forgerock,dc=org
objectClass: sunService
objectClass: top
sunServiceSchema:: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KCjxTZXJ
 2aWNlc0NvbmZpZ3VyYXRpb24+PFNlcnZpY2UgbmFtZT0iZGFzaGJvYXJkU2VydmljZSIgdmVyc2lv
 bj0iMS4wIj48U2NoZW1hIGkxOG5GaWxlTmFtZT0iZGFzaGJvYXJkIiAgaTE4bktleT0iZm9yZ2Vyb
 2NrLWFtLWRhc2hib2FyZC1zZXJ2aWNlLWRlc2NyaXB0aW9uIiAgcmVzb3VyY2VOYW1lPSJkYXNoYm
 9hcmQiICByZXZpc2lvbk51bWJlcj0iMSIgIHNlcnZpY2VIaWVyYXJjaHk9Ii9EU0FNRUNvbmZpZy9
 kYXNoYm9hcmRTZXJ2aWNlIiA+CiAgICAgICAgICAgIDxHbG9iYWwgdmFsaWRhdGU9InllcyIgPgog
 ICAgICAgICAgICAgICAgPEF0dHJpYnV0ZVNjaGVtYSBjb3NRdWFsaWZpZXI9ImRlZmF1bHQiICBpM
 ThuS2V5PSIiICBpc1NlYXJjaGFibGU9Im5vIiAgbGlzdE9yZGVyPSJuYXR1cmFsIiAgbmFtZT0ic2
 VydmljZU9iamVjdENsYXNzZXMiICBzeW50YXg9InN0cmluZyIgIHR5cGU9Imxpc3QiID4KICAgICA
 gICAgICAgICAgICAgICA8RGVmYXVsdFZhbHVlcz4KICAgICAgICAgICAgICAgICAgICAgICAgPFZh
 bHVlPmZvcmdlcm9jay1hbS1kYXNoYm9hcmQtc2VydmljZTwvVmFsdWU+CiAgICAgICAgICAgICAgI
 CAgICAgPC9EZWZhdWx0VmFsdWVzPgogICAgICAgICAgICAgICAgPC9BdHRyaWJ1dGVTY2hlbWE+Cg
 ogICAgICAgICAgICAgICAgPFN1YlNjaGVtYSBoaWRlQ29uZmlnVUk9Im5vIiAgaW5oZXJpdGFuY2U
 9Im11bHRpcGxlIiAgbWFpbnRhaW5Qcmlvcml0eT0ibm8iICBuYW1lPSJkYXNoYm9hcmRBcHAiICBy
 ZWFsbUNsb25lYWJsZT0ieWVzIiAgcmVzb3VyY2VOYW1lPSJpbnN0YW5jZXMiICBzdXBwb3J0c0Fwc
 GxpY2FibGVPcmdhbml6YXRpb249Im5vIiAgdmFsaWRhdGU9InllcyIgPgogICAgICAgICAgICAgIC
 AgICAgIDxBdHRyaWJ1dGVTY2hlbWEgY29zUXVhbGlmaWVyPSJkZWZhdWx0IiAgaTE4bktleT0iRGF

OpenAM ver.12の管理画面で、ユーザ追加を行うと、org.forgerock.openam.idrepo.ldap.DJLDAPv3Repo errorcode=21

以下の通り。

OpenAMの管理画面へ、管理ユーザ(amadmin)へログイン後、 Top Level Realm -> Data Stores -> 「対象」の画面でユーザ追加を行おうとすると

プラグイン org.forgerock.openam.idrepo.ldap.DJLDAPv3Repo で
LDAP 例外が発生しました。LDAP エラーコード = 21

のエラー発生。

forgerock社のサイトを見ると、当時のバグらしい。

なので、openldap-clients を installし、 ldapコマンドラインでユーザ追加を実施することで、解消。

f:id:end0tknr:20200919112928p:plain

step 0 - install openldap-clients

$ sudo yum install openldap-clients

step 1 - ldif ファイルの準備

$ vi add_ldap.ldif

dn: uid=testuser010,ou=People,dc=openam,dc=forgerock,dc=org
uid: testuser010
sn: testuser010
cn: testuser010
userPassword: testtest
mail: 
inetUserStatus: Active
objectClass: iplanet-am-managed-person
objectClass: inetuser
objectClass: sunFederationManagerDataStore
objectClass: sunFMSAML2NameIdentifier
objectClass: inetOrgPerson
objectClass: sunIdentityServerLibertyPPService
objectClass: iPlanetPreferences
objectClass: iplanet-am-user-service
objectClass: organizationalPerson
objectClass: top
objectClass: person
objectClass: sunAMAuthAccountLockout
objectClass: iplanet-am-auth-configuration-service

step 2 - ldapadd コマンドによるユーザ追加

$ /usr/bin/ldapadd -x -h localhost -p 50389 \
>    -D "cn=Directory Manager" -w $ROOTPASSWD \
>    -f ./add_ldap.ldif
adding new entry "uid=testuser101,ou=People,dc=openam,dc=forgerock,dc=org"

step 3 - ldapsearch コマンドによるユーザ一覧表示

$ /usr/bin/ldapsearch -x -h localhost -p 50389  \
    -D "cn=Directory Manager" -w $ROOTPASSWD \
    -b "ou=People,dc=openam,dc=forgerock,dc=org" \
    "uid=*"
# extended LDIF
#
# LDAPv3
# base <ou=People,dc=openam,dc=forgerock,dc=org> with scope subtree
# filter: uid=*
# requesting: ALL
#
<略>
# testuser101, People, openam.forgerock.org
dn: uid=testuser101,ou=People,dc=openam,dc=forgerock,dc=org
objectClass: inetuser
objectClass: iplanet-am-managed-person
objectClass: iplanet-am-auth-configuration-service
objectClass: sunFederationManagerDataStore
objectClass: inetOrgPerson
objectClass: sunFMSAML2NameIdentifier
objectClass: sunIdentityServerLibertyPPService
objectClass: top
objectClass: iPlanetPreferences
objectClass: sunAMAuthAccountLockout
objectClass: organizationalPerson
objectClass: person
objectClass: iplanet-am-user-service
mail:
sn: testuser101
cn: testuser101
userPassword:: e1NTSEF9RXNMYU8vL0NsT09ZZlZJdFRXMFdkOC9LRXluSEllajRMSWtyTXc9PQ=
 =
uid: testuser101
inetUserStatus: Active

# search result
search: 2
result: 0 Success

# numResponses: 3
# numEntries: 2

antlr4-python3-runtime for python3 による java source の parse / 構文解析

java (seasar2) の source を parse / 構文解析 する必要があり、 ANTLR + Python を利用した際のメモ。

qiita.com

と言っても、上記 url の写経です。 上記urlとの違いは、

BasicInfoListener (basic_info_listener.py)内でgetText()した場合、
空白や改行がないtextが取得される為、
AstProcessor (ast_processor.py) で CommonTokenStream から getText()

という点かと思います。

dir 構成

以下に記載の通りですが 「ast_analyze_executor.py」「ast_processor.py」 「basic_info_listener.py」がメインで、 これらについては、以降に記載しています

$ tree
.
│     # src解析する際、command lineから以下のpyを実行
├── ast_analyze_executor.py  ★別途記載
│     # ANTLR から downloadしたjar
├── antlr-4.8-complete.jar
├── antlr-runtime-4.8.jar
│     # logとその設定ですが、自分でも怪しい
├── antlr.log
├── log_conf.yaml
├── ast
│   ├── __init__.py
│   ├── ast_processor.py        ★別途記載
│   ├── basic_info_listener.py  ★別途記載
│   ├── JavaLexer.py
│   ├── JavaParserListener.py
│   └── JavaParser.py
│    # ANTLR の github から downloadした文法file
├── grammar
│   ├── Java8Lexer.g4
│   ├── Java8Lexer.interp
│   ├── Java8Lexer.py
│   ├── Java8Lexer.tokens
│   ├── Java8Parser.g4
│   ├── Java8Parser.interp
│   ├── Java8ParserListener.py
│   ├── Java8Parser.py
│   ├── Java8Parser.tokens
│   ├── JavaLexer.g4
│   ├── JavaLexer.interp
│   ├── JavaLexer.py
│   ├── JavaLexer.tokens
│   ├── JavaParser.g4
│   ├── JavaParser.interp
│   ├── JavaParserListener.py
│   ├── JavaParser.py
│   └── JavaParser.tokens
│ # test解析に使用した
│ # 「Seasar2徹底入門 - SAStruts/S2JDBC対応 - 翔泳社」のサンプルコード
└── CsvAction.java

python script

ast_analyze_executor.py

# -*- coding: utf-8 -*-

import logging.config
from ast.ast_processor import AstProcessor
from ast.basic_info_listener import BasicInfoListener
import sys
import yaml
import pprint

log_conf = './log_conf.yaml'  # log設定は自分でも怪しいと思う

def main():
    logging.config.dictConfig(yaml.load(open(log_conf).read(),
                                        Loader=yaml.SafeLoader))
    logger = logging.getLogger('mainLogger')

    target_file_path = sys.argv[1]
    ast_info = \
        AstProcessor(logging, BasicInfoListener()).execute(target_file_path)

    print(pprint.pformat(ast_info, width=80)) # 幅:80文字に整形
#    pprint.pprint(ast_info)


if __name__ == "__main__":
    main()

ast.ast_processor.py

# -*- coding: utf-8 -*-
from antlr4 import FileStream, CommonTokenStream, ParseTreeWalker
from ast.JavaLexer import JavaLexer
from ast.JavaParser import JavaParser
import pprint

source_encode = "utf-8"

class AstProcessor:

    def __init__(self, logging, listener):
        self.logging = logging
        self.logger = logging.getLogger(self.__class__.__name__)
        self.listener = listener

    def execute(self, input_source):
        file_stream = FileStream(input_source,encoding=source_encode)
        common_token_stream = CommonTokenStream(JavaLexer(file_stream))
        
        
        parser = JavaParser(common_token_stream)
        walker = ParseTreeWalker()
        walker.walk(self.listener, parser.compilationUnit())

        ast_info = self.listener.ast_info

        # BasicInfoListener 内で getText()した場合、
        # 空白や改行がないtextが取得される為、ここで
        # CommonTokenStream から getText() します
        for method in ast_info['methods']:
            pprint.pprint(method)

            start_index = method['body_pos']['start_index']
            stop_index  = method['body_pos']['stop_index']
            
            method['body_src'] = \
                common_token_stream.getText(start_index,stop_index)
        
        return ast_info

ast.basic_info_listener.py

# -*- coding: utf-8 -*-
from ast.JavaParserListener import JavaParserListener
from ast.JavaParser import JavaParser
import re
import sys
import pprint

class BasicInfoListener(JavaParserListener):
    def __init__(self):
        self.ast_info = {'packageName': '',
                         'className'  : '',
                         'annotation' : [],
                         'modifier'   : [],
                         'implements' : [],
                         'extends'    : '',
                         'imports'    : [],
                         'fields'     : [],
                         'methods'    : []}
        self.tmp_annotation = []
        self.tmp_modifier   = []

    def enterPackageDeclaration(self, ctx):
        # pprint.pprint(sys._getframe().f_code.co_name+' '+ctx.getText())
        self.ast_info['packageName'] = ctx.qualifiedName().getText()
        pprint.pprint(self.ast_info['packageName'])

    def enterImportDeclaration(self, ctx):
        # pprint.pprint(sys._getframe().f_code.co_name+' '+ctx.getText())
        import_class = ctx.qualifiedName().getText()
        self.ast_info['imports'].append(import_class)

    def enterClassOrInterfaceModifier(self, ctx):
        pprint.pprint(sys._getframe().f_code.co_name+' '+ ctx.getText())
        if re.match('^@',ctx.getText()):
            self.tmp_annotation.append(ctx.getText())
        else :
            self.tmp_modifier.append(ctx.getText())
        
    def enterClassDeclaration(self, ctx):
        # pprint.pprint(sys._getframe().f_code.co_name+' '+ctx.getText())
        
        self.ast_info['annotation'] += self.tmp_annotation
        self.ast_info['modifier']   += self.tmp_modifier
        self.tmp_annotation = []
        self.tmp_modifier = []
        
        child_count = int(ctx.getChildCount())
        if child_count == 7:
            # c1 = ctx.getChild(0)                      # class
            c2 = ctx.getChild(1).getText()              # class name
            c3 = ctx.getChild(2)                        # extends
            c4 = ctx.getChild(3).getChild(0).getText()  # extends class name
            c5 = ctx.getChild(4)                        # implements
            c7 = ctx.getChild(6)                        # class body
            
            self.ast_info['className']  = c2
            self.ast_info['extends']    = c4
            self.ast_info['implements'] = \
            self.parse_implements_block(ctx.getChild(5))
            return
        
        if child_count == 5:
            c1 = ctx.getChild(0)                        # class
            c2 = ctx.getChild(1).getText()              # class name
            c3 = ctx.getChild(2).getText()              # extends or implements
            c5 = ctx.getChild(4)                        # class body

            
            self.ast_info['className'] = c2
            if c3 == 'implements':
                self.ast_info['implements'] = \
                    self.parse_implements_block(ctx.getChild(3))
            elif c3 == 'extends':
                c4 = ctx.getChild(3).getChild(0).getText()
                self.ast_info['extends'] = c4
            return
        
        if child_count == 3:
            c1 = ctx.getChild(0)                        # class
            c2 = ctx.getChild(1).getText()              # class name
            c3 = ctx.getChild(2)                        # class body
            self.ast_info['className'] = c2
            
            return
        
        print("unknown child_count"+ str(child_count))
        sys.exit()
        
    
    def enterFieldDeclaration(self, ctx):
        # pprint.pprint(sys._getframe().f_code.co_name+' '+ctx.getText())
        field = {'fieldType'      : ctx.getChild(0).getText(),
                 'fieldDefinition': ctx.getChild(1).getText(),
                 'annotation'     : [],
                 'modifier'       : []  }
        
        field['annotation'] += self.tmp_annotation
        field['modifier']   += self.tmp_modifier
        self.tmp_annotation = []
        self.tmp_modifier = []
        
        self.ast_info['fields'].append(field)

#    def exitFieldDeclaration(self, ctx): pass
        
    def enterMemberDeclaration(self, ctx): pass
        # pprint.pprint(sys._getframe().f_code.co_name+' '+ctx.getText())
        # print("{0} {1} {2} {3}".format(sys._getframe().f_code.co_name,
        #                                ctx.start.line,
        #                                ctx.start.column,
        #                                ctx.getText()))

    def enterMethodDeclaration(self, ctx):
        # pprint.pprint(sys._getframe().f_code.co_name+' '+ctx.getText())
        
        c1 = ctx.getChild(0).getText()  # return type
        c2 = ctx.getChild(1).getText()  # method name
        # params
        params = self.parse_method_params_block(ctx.getChild(2))

        # method bodyを CommonTokenStream と tokenIndex により得る為
        ctx_method_body = ctx.getChild(-1)


        method_info = {'returnType': c1,
                       'methodName': c2,
                       'annotation': [],
                       'modifier'  : [],
                       'params': params,
                       'body_pos' : {
                           'start_line'  : ctx_method_body.start.line,
                           'start_column': ctx_method_body.start.column,
                           'start_index' : ctx_method_body.start.tokenIndex,
                           'stop_line'   : ctx_method_body.stop.line,
                           'stop_column' : ctx_method_body.stop.column,
                           'stop_index'  : ctx_method_body.stop.tokenIndex}}
        method_info['annotation'] += self.tmp_annotation
        method_info['modifier']   += self.tmp_modifier
        self.tmp_annotation = []
        self.tmp_modifier = []
        self.ast_info['methods'].append(method_info)

        # cols = []
        # for child in ctx.getChildren():
        #     cols.append(child.getText())
        # print("HOGEHOGE:"+" ".join(cols))
        
           
    def parse_implements_block(self, ctx):
        implements_child_count = int(ctx.getChildCount())
        result = []
        if implements_child_count == 1:
            impl_class = ctx.getChild(0).getText()
            result.append(impl_class)
        elif implements_child_count > 1:
            for i in range(implements_child_count):
                if i % 2 == 0:
                    impl_class = ctx.getChild(i).getText()
                    result.append(impl_class)
        return result

    def parse_method_params_block(self, ctx):
        params_exist_check = int(ctx.getChildCount())
        result = []
        if params_exist_check == 3:
            params_child_count = int(ctx.getChild(1).getChildCount())
            if params_child_count == 1:
                param_type = ctx.getChild(1).getChild(0).getChild(0).getText()
                param_name = ctx.getChild(1).getChild(0).getChild(1).getText()
                param_info = {'paramType': param_type,
                              'paramName': param_name }
                result.append(param_info)
            elif params_child_count > 1:
                for i in range(params_child_count):
                    if i % 2 == 0:
                        param_type = \
                            ctx.getChild(1).getChild(i).getChild(0).getText()
                        param_name = \
                            ctx.getChild(1).getChild(i).getChild(1).getText()
                        param_info = {'paramType': param_type,
                                      'paramName': param_name }
                        result.append(param_info)
        return result

実行結果

$ /usr/local/python3/bin/python3 ast_analyze_executor.py CsvAction.java
'org.seasar.sastruts.example.action'
'enterClassOrInterfaceModifier public'
'enterClassOrInterfaceModifier @Resource'
'enterClassOrInterfaceModifier @ActionForm'
'enterClassOrInterfaceModifier protected'
'enterClassOrInterfaceModifier @Resource'
'enterClassOrInterfaceModifier protected'
'enterClassOrInterfaceModifier @Resource'
'enterClassOrInterfaceModifier protected'
'enterClassOrInterfaceModifier @Resource'
'enterClassOrInterfaceModifier protected'
'enterClassOrInterfaceModifier public'
'enterClassOrInterfaceModifier public'
'enterClassOrInterfaceModifier @Execute(validator=false)'
'enterClassOrInterfaceModifier public'
'enterClassOrInterfaceModifier @Execute(validator=true,input="index.jsp")'
'enterClassOrInterfaceModifier public'
{'annotation': ['@Execute(validator=false)'],
 'body_pos': {'start_column': 45,
              'start_index': 310,
              'start_line': 48,
              'stop_column': 4,
              'stop_index': 380,
              'stop_line': 62},
 'methodName': 'index',
 'modifier': ['public'],
 'params': [],
 'returnType': 'String'}
{'annotation': ['@Execute(validator=true,input="index.jsp")'],
 'body_pos': {'start_column': 42,
              'start_index': 407,
              'start_line': 65,
              'stop_column': 4,
              'stop_index': 583,
              'stop_line': 90},
 'methodName': 'read',
 'modifier': ['public'],
 'params': [],
 'returnType': 'String'}
{'annotation': [],
 'className': 'CsvAction',
 'extends': '',
 'fields': [{'annotation': ['@Resource', '@ActionForm'],
             'fieldDefinition': 'csvForm',
             'fieldType': 'CsvForm',
             'modifier': ['protected']},
            {'annotation': ['@Resource'],
             'fieldDefinition': 'csvService',
             'fieldType': 'CsvService',
             'modifier': ['protected']},
            {'annotation': ['@Resource'],
             'fieldDefinition': 's2csvCtrlFactory',
             'fieldType': 'S2CSVCtrlFactory',
             'modifier': ['protected']},
            {'annotation': ['@Resource'],
             'fieldDefinition': 'request',
             'fieldType': 'HttpServletRequest',
             'modifier': ['protected']},
            {'annotation': [],
             'fieldDefinition': 'deptList',
             'fieldType': 'List<DeptCsv>',
             'modifier': ['public']},
            {'annotation': [],
             'fieldDefinition': 'csv',
             'fieldType': 'String',
             'modifier': ['public']}],
 'implements': [],
 'imports': ['java.io.IOException',
             'java.io.InputStream',
             'java.io.StringWriter',
             'java.util.List',
             'javax.annotation.Resource',
             'javax.servlet.http.HttpServletRequest',
             'org.apache.commons.io.IOUtils',
             'org.apache.struts.action.ActionErrors',
             'org.seasar.framework.util.StringUtil',
             'org.seasar.s2csv.csv.S2CSVWriteCtrl',
             'org.seasar.s2csv.csv.factory.S2CSVCtrlFactory',
             'org.seasar.sastruts.example.csv.DeptCsv',
             'org.seasar.sastruts.example.form.CsvForm',
             'org.seasar.sastruts.example.service.CsvService',
             'org.seasar.struts.annotation.ActionForm',
             'org.seasar.struts.annotation.Execute',
             'org.seasar.struts.util.ActionMessagesUtil'],
 'methods': [{'annotation': ['@Execute(validator=false)'],
              'body_pos': {'start_column': 45,
                           'start_index': 310,
                           'start_line': 48,
                           'stop_column': 4,
                           'stop_index': 380,
                           'stop_line': 62},
              'body_src': '{\r\n'
                          '\r\n'
                          '        InputStream in = Thread.currentThread()\r\n'
                          '            .getContextClassLoader()\r\n'
                          '            '
                          '.getResourceAsStream("/data/dept.csv");\r\n'
                          '\r\n'
                          '        try {\r\n'
                          '            this.csvForm.csvData = '
                          'IOUtils.toString(in, "UTF-8");\r\n'
                          '\r\n'
                          '        } finally {\r\n'
                          '            IOUtils.closeQuietly(in);\r\n'
                          '        }\r\n'
                          '\r\n'
                          '        return "index.jsp";\r\n'
                          '    }',
              'methodName': 'index',
              'modifier': ['public'],
              'params': [],
              'returnType': 'String'},
             {'annotation': ['@Execute(validator=true,input="index.jsp")'],
              'body_pos': {'start_column': 42,
                           'start_index': 407,
                           'start_line': 65,
                           'stop_column': 4,
                           'stop_index': 583,
                           'stop_line': 90},
              'body_src': '{\r\n'
                          '        ActionErrors errors = new '
                          'ActionErrors();\r\n'
                          '\r\n'
                          '        '
                          'if(StringUtil.isNotEmpty(this.csvForm.processAll)){\r\n'
                          '            // 一括で処理\r\n'
                          '            this.deptList = '
                          'csvService.parseAll(this.csvForm.csvData, '
                          'errors);\r\n'
                          '        } else {\r\n'
                          '            // 1件ずつ処理\r\n'
                          '            this.deptList = '
                          'csvService.parse(this.csvForm.csvData, errors);\r\n'
                          '        }\r\n'
                          '\r\n'
                          '        if(!errors.isEmpty()){\r\n'
                          '            ActionMessagesUtil.addErrors(request, '
                          'errors);\r\n'
                          '            return "index.jsp";\r\n'
                          '        }\r\n'
                          '\r\n'
                          '        StringWriter writer = new '
                          'StringWriter();\r\n'
                          '\r\n'
                          '        S2CSVWriteCtrl<DeptCsv> controller\r\n'
                          '            = '
                          's2csvCtrlFactory.getWriteController(DeptCsv.class, '
                          'writer);\r\n'
                          '\r\n'
                          '        controller.writeAll(this.deptList);\r\n'
                          '        this.csv = writer.toString();\r\n'
                          '\r\n'
                          '        return "read.jsp";\r\n'
                          '    }',
              'methodName': 'read',
              'modifier': ['public'],
              'params': [],
              'returnType': 'String'}],
 'modifier': ['public'],
 'packageName': 'org.seasar.sastruts.example.action'}

再 LWP::UserAgentfor perl でリダイレクト先のコンテンツを取得

2010年に上記※1を記載しましたが、※2によれば、 LWP::UserAgent->new(%option) の %option には次があるらしい。

KEY default note
agent "libwww-perl/#.###" USER_AGENT
cookie_jar undef 使ったことはあります
timeout 180
max_size undef max contents size
requests_redirectable ['GET', 'HEAD'] redirectする対象method
max_redirect 7 max redirect count
protocols_allowed undef 許可protocolらしいが...
protocols_forbidden undef 禁止protocolらしいが...
from undef mail address?
default_headers HTTP::Headers->new HTTP::Headers object?
conn_cache undef 何だろう?
parse_head 1 何だろう?

特に、redirect に関しては、requests_redirectable や max_redirect が ありますので、上記※1は、以下のようになると思います。

#!/usr/local/bin/perl
use strict;
use warnings;
use HTTP::Request::Common;
use LWP::UserAgent;
use Encode;
use CGI;

# redirectを発生させる仮?のcgiを指定
my $TARGET_CGI = "http://cent80.a5.jp:8080/Test/searchto.pl";

main();

sub main {
    my $ua = LWP::UserAgent->new;

    # 301や302検知後、redirectする場合の対象method
    #   refer to https://soft.bko.to/perl/K-M/LWP-UserAgent.html
    $ua->requests_redirectable(["POST","GET"]);
  
    my $req_data =[limit              =>20,
           offset             =>20,
           blog_id            =>2,
           search             =>'("plantype|P1" OR "plantype|P2")',
           SearchSortBy       =>"text",
           SearchResultDisplay=>"descend",
           searchFields       =>"title,more"];

    my $req = HTTP::Request::Common::POST($TARGET_CGI, $req_data);
    $req->header("X-Requested-With"  =>'XMLHttpRequest',
         "X-MT-Authorization"=>'MTAuth accessToken=HOGEHOGE');
    
    my $res = $ua->request($req);

    # requests_redirectable([]) の様に空にした場合、以下のifがtrue
    if ( $res->is_redirect ) {  # redirectの場合
        print "REDIRECT DETECTED\n";
        print $res->as_string, "\n";
        return;
    }

    if ( $res->is_success ) {
        print $res->content, "\n";
        return;
    }
}

javascript - Geolocation API で緯度経度を取得し、更に google map api で、緯度経度→住所 変換

住所フォームへの入力の手間を軽減できないかと考えてみた。

動作はしますが、表示される住所精度は、PCやスマホ等の利用環境に影響を受ける為、 自動入力後、手修正が必要。

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>input address test</title>
</head>
<body>
  <h1>end0tknr's input address test</h1>

  ブラウザの Geolocation API で緯度・経度を取得し、<br/>
  更に google map api で、緯度・経度→住所 変換します。<br/>
  表示される住所精度は、PCやスマホ等の利用環境に影響を受ける為、<br/>
  自動入力後、手修正を行って下さい。<br/>
  
  <form method="post">
    <table>
      <thead>
    <tr><td colspan="2">
        <button type="button" onClick="myform.input_address();">GET ADDRESS</button>
    </td></tr>
      </thead>
      <tbody>
    <tr>
      <td>緯度・経度</td>
      <td><input id="lat" type="text" style="width:95px;">
          <input id="lng" type="text" style="width:95px;">
      </td>
    </tr>
    <tr>
      <td>郵便番号</td>
      <td><input id="zipcode" type="text" style="width:200px;"></td>
    </tr>
    <tr>
      <td>都道府県</td>
      <td><input id="pref" type="text" style="width:200px;"></td>
    </tr>
    <tr>
      <td>市区町村</td>
      <td><input id="city" type="text" style="width:200px;"></td>
    </tr>
    <tr>
      <td>その他  </td>
      <td><input id="address_other" type="text" style="width:200px;"></td>
    </tr>
      </tbody>
    </table>
  </form>
  <script src="//cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
  <script src="form.js"></script>
</body>
</html>
(function() {
    var MyForm = function() {};
    MyForm.prototype = {
    gmap_api_url :'https://maps.googleapis.com/maps/api/geocode/json',
    gmap_api_opt :{"key"     :"AIza ないしょ sMu4",
               "language":"ja",
               "sensor"  :false},
        
    input_address: function(){
        if( ! navigator.geolocation ) { // Geolocation API 対応check
        alert( "ご利用のブラウザは、現在位置(緯度/軽度)を取得できません。");
        return false;
        }

        var this_obj = this;
        navigator.geolocation.getCurrentPosition(
        function(position){this_obj.post_current_pos_success(position)},
        function(error   ){this_obj.post_current_pos_error(error)},
        {"enableHighAccuracy": false,
         "timeout"   : 8000,
         "maximumAge": 2000});
    },

    post_current_pos_success: function(position){
        var lat = position.coords.latitude;
        var lng = position.coords.longitude;
        var acc_latlng = position.coords.accuracy;
        $("#lat").val(lat);
        $("#lng").val(lng);

        var req_data = this.gmap_api_opt;
        console.log(req_data);
        req_data["latlng"] = lat+","+lng;
        
            $.ajax({url: this.gmap_api_url,
                    type: 'GET',
                    data: req_data })
                .done( (data) => { this.post_conv_latlng_to_address(data) })
                .fail( (data) => { alert("error",data)  });
    },

    post_current_pos_error: function(error){
        var err_msgs = {0: "原因不明のエラーです" ,
                1: "位置情報の取得が未許可です" ,
                2: "通信状況等により位置情報取得に失敗" ,
                3: "位置情報取得がtimeout"};
        alert( err_msgs[error.code] );
    },
    

    post_conv_latlng_to_address: function(data){
        if (data.status != "OK"){
        alert("緯度・経度→住所返還に失敗しました");
        return false;
        }

        for (var i=0; i<data.results.length; i++) {
        var addr_compo = data.results[i].address_components.reverse();
        
        if(!addr_compo[0].long_name.match( /^\d{3}-?\d{4}$/g ) ||
           addr_compo[1].long_name != "日本" ){
            continue;
        }
        console.log(data.results[i]);
        
        $("#zipcode").val(addr_compo[0].long_name);
        $("#pref").val(addr_compo[2].long_name);
        $("#city").val(addr_compo[3].long_name);

        var formatted_address = data.results[i].formatted_address;
        var addr_header =
            "日本、〒" + addr_compo[0].long_name + " " +
            addr_compo[2].long_name +
            addr_compo[3].long_name;
        var address_other = formatted_address.replace(addr_header, '');
        $("#address_other").val( address_other );
        break;
        }
        
    }
    }
    window.myform = new MyForm();
})();

centos8 on aws へ ssh する際のlogin user は、centos

以下の通り、user=centos であれば、ssh可能です

$ ssh -i ~/.ssh/end0tknr_202008.pem centos@18.183.233.???
Activate the web console with: systemctl enable --now cockpit.socket

Last login: Sun Aug 30 07:53:17 2020 from 61.24.125.???
[centos@ip-172-31-6-60 ~]$ 

ちなみに、os=amazon linux2 のように ec2-user を利用すると、エラーとなります

$ ssh -i ~/.ssh/end0tknr_202008.pem ec2-user@18.183.233.???
ec2-user@18.183.233.???: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

更に、root で ssh接続しようとすると、user=centosの利用を促されます

$ ssh -i ~/.ssh/end0tknr_202008.pem root@18.183.233.???
Please login as the user "centos" rather than the user "root".

Connection to 18.183.233.??? closed.
$ 

AWS における CentOS8 by centos.org 利用は、Ami ID で直接検索

2020/8時点、AWS Console の EC2 新規作成画面において「centos8」で検索しても centos.org による centos8 は、すぐには見つかりません。 ( centos.org 以外による centos8 の方が先に見つかると思います )

f:id:end0tknr:20200830155350p:plain

どうやら https://wiki.centos.org/Cloud/AWS によれば、 os image の提供方法は、 「Official and current CentOS Public Images」と 「Legacy CentOS Public Images」で異なり、 「Official and current CentOS Public Images」は 「published outside of the AWS Market Place and are shared directly」のようです。

f:id:end0tknr:20200830155418p:plain

f:id:end0tknr:20200830155429p:plain

なので...東京リージョン=ap-northeast-1 の Ami ID=ami-089a156ea4f52a0a3 は EC2 新規作成画面において「ami-089a156ea4f52a0a3」で検索することで選択できます。

f:id:end0tknr:20200830155520p:plain

その他、「EC2 Image Builder」画面で custom AMI ID=ami-089a156ea4f52a0a3とすることでも選択できます。

f:id:end0tknr:20200830155556p:plain

Sublist3r for python によるsubdomain 探索 サブドメイン

以下の通り。後はご自身でお試し下さい

https://github.com/aboul3la/Sublist3r

以下、install & 実行

git clone https://github.com/aboul3la/Sublist3r.git
cd Sublist3r

## 依存library install
$ sudo /usr/local/python3/bin/pip install -r requirements.txt 

## 実行
$ /usr/local/python3/bin/python3 sublist3r.py -d example.com

                 ____        _     _ _     _   _____
                / ___| _   _| |__ | (_)___| |_|___ / _ __
                \___ \| | | | '_ \| | / __| __| |_ \| '__|
                 ___) | |_| | |_) | | \__ \ |_ ___) | |
                |____/ \__,_|_.__/|_|_|___/\__|____/|_|

                # Coded By Ahmed Aboul-Ela - @aboul3la
    
[-] Enumerating subdomains now for example.com
[-] Searching now in Baidu..
[-] Searching now in Yahoo..
[-] Searching now in Google..
[-] Searching now in Bing..
[-] Searching now in Ask..
[-] Searching now in Netcraft..
[-] Searching now in DNSdumpster..
[-] Searching now in Virustotal..
[-] Searching now in ThreatCrowd..
[-] Searching now in SSL Certificates..
[-] Searching now in PassiveDNS..
sublist3r.py:614: DeprecationWarning: please use dns.resolver.Resolver.resolve() instead
  ip = Resolver.query(host, 'A')[0].to_text()
[-] Total Unique Subdomains Found: 16267
www.example.com
*.example.com
servers2.0.example.com
servers21.0.example.com
sabelfeld.00.example.com
testovyy1.yashchik.00.example.com
  :
<略 example.com の サブドメインはあまりに大量>  

openam 12 における 持続cookie の設定

forgerock社のdocumentによれば、上記urlで 「iPSPCookie」「 DProPCookie」によるページ内検索。 または、「2.3.18. Hints for the Persistent Cookie Module」を読めば、よいのかも。

ただ、ssoadm コマンドでは

set-realm-svc-attrs -e sso -s iPlanetAMAuthService \
  -a "iplanet-am-auth-persistent-cookie-mode=true"
set-realm-svc-attrs -e sso -s iPlanetAMAuthService \
  -a "iplanet-am-auth-persistent-cookie-time=5184000"

により持続cookie を有効化した記憶がありますが、 「iplanet-am-auth-persistent-cookie-mode=true」に該当する画面はどこだろう?

clang-format による java source code 整形

clang-format は、コマンドラインでも使用できるようですので、メモ

https://clang.llvm.org/docs/ClangFormat.html

https://clang.llvm.org/docs/ClangFormatStyleOptions.html

install from source code もできますが...

以下の手順で、src からinstallできますが、makeに数時間や数GB(5GB超?)を要する為、 私が使用する centos8 on virtualboxでは、build できませんでした。

$ wget https://github.com/llvm/llvm-project/archive/llvmorg-10.0.1.tar.gz
$ tar -xvf llvmorg-10.0.1.tar.gz
$ cd llvm-project-llvmorg-10.0.1
$ mkdir build
$ cd build
$ cmake -G "Unix Makefiles" ../llvm
$ make

install from package

$ sudo yum install clang*
$ /usr/bin/clang-format --version
clang-format version 9.0.1 (Red Hat 9.0.1-2.module_el8.2.0+309+0c7b6b03)

formatting java source code

以下は、foo.java を indent幅=4 で、上書き(-i)するものです。

clang-format -i -style="{IndentWidth: 4}" ./foo.java

その他、標準入力でsource code を受け取ることもできます。 以下は、「Seasar2徹底入門 - SAStruts/S2JDBC対応 - 翔泳社」のサンプルコードを 整形した例です。

https://www.shoeisha.co.jp/book/detail/9784798121505

$ cat foo.java | clang-format --assume-filename="foo.java" -style="{IndentWidth: 4}"
package org.seasar.sastruts.example.action;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.seasar.fisshplate.preview.FPPreviewUtil;
import org.seasar.fisshplate.template.FPTemplate;
import org.seasar.sastruts.example.fpao.HelloFpao;
import org.seasar.sastruts.example.fpao.HelloFpao.HelloDto;
import org.seasar.struts.annotation.Execute;
import org.seasar.struts.util.ResponseUtil;

/**
 * Fisshplateのサンプルです。
 *
 * @author Naoki Takezoe
 */
public class ExcelAction {
    @Resource protected HelloFpao helloFpao;

    /**
     * Fisshplateのサンプル一覧を表示します。
     */
    @Execute(validator = false)
    public String index() {
        return "index.jsp";
    }

    /**
     * バインド変数のサンプルです。
     */
    @Execute(validator = false)
    public String helloWorld() throws Exception {
        InputStream in =
            getClass().getResourceAsStream("/excel/HelloWorld.xls");
        FPTemplate template = new FPTemplate();

        Map<String, String> map = new HashMap<String, String>();
        map.put("name", "たけぞう");

        HSSFWorkbook wb = template.process(in, map);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        wb.write(out);

        ResponseUtil.download("HelloWorld.xls", out.toByteArray());

        return null;
    }
   :
    /**
     * プレビュー機能のサンプルです。
     */
    @Execute(validator = false)
    public String preview() throws Exception {
        InputStream template =
            getClass().getResourceAsStream("/excel/Foreach.xls");
        InputStream data =
            getClass().getResourceAsStream("/excel/Foreach_Preview.xls");

        HSSFWorkbook wb = FPPreviewUtil.getWorkbook(template, data);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        wb.write(out);

        ResponseUtil.download("Foreach_Preview.xls", out.toByteArray());

        return null;
    }
}
$ 

logging.config for python の 設定fileを yaml で記載

python の logging には、様々な設定が可能ですが、とりあえずは、以下で動作します。

# -*- coding: utf-8 -*-

import logging.config
import yaml

log_conf = './log_conf.yaml'

def main():
    logging.config.dictConfig(yaml.load(open(log_conf).read(),
                                        Loader=yaml.SafeLoader))

    logger = logging.getLogger('mainLogger')
    logger.error('hogehoge')

if __name__ == "__main__":
    main()
version: 1

formatters:
  mainFormatter:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    datefmt: '%Y/%m/%d %I:%M:%S'

handlers:
  file:
    class: logging.handlers.RotatingFileHandler
    level: INFO
    formatter: mainFormatter
    filename: antlr.log
    maxBytes: 1000
    backupCount: 1

loggers:
  mainLogger:
    level: INFO
    handlers: [file]
    propagate: no

root:
  level: INFO
  handlers: []

IdP by OpenAM - SP by OpenAM による SAML連携

SAMLシングル・サインオン構成において、 IdP(Identity Provider)にのみ OpenAM を用い、 SP(Service Provider)にOpenAM以外(SalesforceやG-Suite、cybozu)を用いた例は、 インターネット上によく見かけます。

一方、SP に OpenAMを用いた例は、あまり見かけない為、 今回、主に SPや SP側WebAgentの設定について記載しています。

f:id:end0tknr:20200822094024p:plain

0.今回 構成のポイント

https://resources.us.safenetid.com/help/ForgeRock%20OpenAM/Index.htm

上記urでは、SP に OpenAM を用い、 Authentication Modules (SAML)や Authentication Chain を 設定し、 SP にあるログイン画面を表示しないようにしています。(下図)

f:id:end0tknr:20200822094045p:plain

当初、上記urlの設定に倣おうとしましたが、上手くSAML連携できなかった為、 今回は、Auth Moduleや Auth Chain を用いず、 主に SAMLトラストサークルと、ログイン画面URLの設定を行うことで IdPのログイン画面をdefault表示するようにしていますので、 SPのログイン画面もURLを直接指定することで表示できる状態にしています。

1. 事前準備

ここでは、SPやSP側WebAgentのサーバインスタンスの追加から、 ミドルウェアやOpenAMのインストールまでを簡単に記載します。

1.1. OpenAM SP - sp.end0tknr.com

1.1.1. サーバインスタンス

AWS EC2

t2.small、ディスク=50GB、OS=AmazonLinux1

hostname

$ sudo vi /etc/sysconfig/network
   HOSTNAME=sp1.end0tknr.com

(ロードバランサ下での冗長構成を想定し、上記としてます)

/etc/hosts

$ sudo vi /etc/hosts
  $PRIVATE_IP sp1.end0tknr.com

( $PRIVATE_IPはAWSにより決定される任意のものです)

ALB(ELB)

ホスト名=sp.end0tknr.com
TARGET= sp1.end0tknr の port:8080

1.1.2. Java

version 1.8.0を利用していますが、別のバージョンでも問題ないと思います。

$ sudo yum install java-1.8.0-openjdk-devel.x86_64
$ sudo alternatives --config java
$ /usr/bin/java -version
openjdk version "1.8.0_252"

1.1.3. tomcat

少々、古めのOpenAM 13を使用する為、tomcat7を使用しています

$ cd /opt
$ sudo wget https://ftp.tsukuba.wide.ad.jp/software/apache/tomcat/tomcat-7/v7.0.105/bin/apache-tomcat-7.0.105.tar.gz
$ sudo tar -xvf apache-tomcat-7.0.105.tar.gz
$ sudo ln -s apache-tomcat-7.0.105 tomcat

$ sudo useradd tomcat
$ sudo chown -R tomcat:tomcat /opt/tomcat
$ sudo chown -R tomcat:tomcat /opt/apache-tomcat-7.0.105

$ sudo mkdir /opt/sso  ## openamの設定用dir
$ sudo chown tomcat:tomcat /opt/sso

$ sudo vi /etc/init.d/tomcat
  #!/bin/bash
  # description: Tomcat7 Start Stop Restart
  # processname: tomcat7
  # chkconfig: 234 20 80
  CATALINA_HOME=/opt/tomcat

  case $1 in
  start)
      sh $CATALINA_HOME/bin/startup.sh
  ;;
  stop)
      sh $CATALINA_HOME/bin/shutdown.sh
  ;;
  restart)
      sh $CATALINA_HOME/bin/shutdown.sh
      sh $CATALINA_HOME/bin/startup.sh
  ;;
  esac

1.1.4. OpenAM

OpenAM ver.12 を使用したかったのですが、 ForgeRock社のライセンス変更?により入手できない為、 OpenAM Community Edition ver.13 を使用しています。

$ sudo /etc/init.d/tomcat stop

$ sudo su -
# cd /opt/tomcat/webapps
# wget https://github.com/OpenIdentityPlatform/OpenAM/releases/download/13.0.0/OpenAM-13.0.0.war
# mv OpenAM-13.0.0.war sso.war
# chown tomcat:tomcat sso.war

$ sudo /etc/init.d/tomcat start

https://sp.end0tknr.com/sso へブラウザでアクセス

- 「設定オプション」画面にて「カスタム設定の新しい設定の作成」リンクをクリック

- 「カスタム設定オプション 手順1:一般」にて
  「amadmin」のpasswordを<ないしょ>に設定

- 「カスタム設定オプション 手順2:サーバ設定」にて
   サーバURL     : https://sp.end0tknr.com:443
   Cookieドメイン: .end0tknr.com
   ロケール      : en_US
   設定dir       : /opt/sso
   
- 「カスタム設定オプション 手順3:設定データストア設定」にて
   設定データストア  : OpenAM
   ポート            : 50389
   管理者ポート      : 4444
   JMXポート         : 1689
   暗号化鍵          : root4HEMS
   ルートサフィックス: dc=openam,dc=forgerock,dc=org

- 「カスタム設定オプション 手順4:ユーザーデータストア設定」にて
  「OpenAMのユーザデータストア」を選択

- 「カスタム設定オプション 手順5:サイト設定」に
   ロードバランサの背後に配備され「ない」として次へ
   ※「ロードバランサの背後に配備される」でも問題ないと思います。

- 「カスタム設定オプション 手順6:デフォルトのポリシーエージェントユーザー」にて
   パスワード : <ないしょ>

「設定の作成」をクリックし、しばらく経過すると「設定が完了しました」と
表示される為、「ログインに進む」をクリックでログインできれば、完了。

1.1.5.テスト用ユーザの作成

ブラウザでOpenAMにログインし、IdPにて、Top Level Realm -> Data Stores -> 「対象」順に画面遷移後、ユーザを追加して下さい。

1.2. OpenAM SP側WebAgent - sp-agt.end0tknr.com

1.2.1. サーバインスタンス

AWS EC2

t2.small、ディスク=50GB、OS=AmazonLinux1

hostname

$ sudo vi /etc/sysconfig/network
   HOSTNAME=sp-agt1.end0tknr.com

(ロードバランサ下での冗長構成を想定し、上記としてます)

/etc/hosts

$ sudo vi /etc/hosts
  $PRIVATE_IP sp-agt1.end0tknr.com

( $PRIVATE_IPはAWSにより決定される任意のものです)

ALB(ELB)

ホスト名=sp-agt.end0tknr.com
TARGET= sp-agt1.end0tknr の port:8080

1.2.2. apache httpd

$ sudo yum install httpd24
$ /usr/sbin/httpd -v
Server version: Apache/2.4.43 (Amazon)

$ sudo chkconfig --add httpd
$ sudo chkconfig httpd on
$ sudo chkconfig --list httpd
httpd    0:off  1:off   2:on    3:on    4:on    5:on    6:off

$ sudo /etc/init.d/httpd start

1.2.3. WebAgent (Servie Provider側)

先程、installした Service Provider (OpenAM)へブラウザでログインし、 以下の設定を行って下さい。

エージェントプロファイルを作成。
Top Level Realm -> Agents -> Web で遷移した画面で、
エージェントの「新規」をクリックし、「新しいWeb」の画面にて以下を入力

  名前            : idp_to_sp_by_saml
  パスワード      : ないしょ
  設定            : 集中
  サーバー URL    : https://sp.end0tknr.com:443/sso
  エージェント URL: https://sp-agt.end0tknr.com:443/

同じ IdPにて、OpenAM上でAuthorization Policyを作成。
Top Level Realm -> Authorization -> Policy Setsへ移動。
iPlanetAMWebAgentServiceの右側のEdit(鉛筆)アイコンをクリック。
Add a New policyで新しいポリシーを作成する。
「New Policy」の画面にて以下を入力。

  Name        : idp_to_sp_by_saml
  Description : null
  Resources   : *://*:*/*

更に「Subjects」タブへ移動し、TypeをAuthenticated Usersに変更。

1.2.4. WebAgent

※yum install httpd で adduser された apache を su - 可能にする(以下)
# su - apache
This account is currently not available.
# vi /etc/passwd
apache:x:48:48:Apache:/var/www:/bin/bash  # CHANGE from /sbin/nologin

$ sudo su -
$ mkdir /etc/httpd/openam
$ chown apache:apache /etc/httpd/openam

## 以下は、web agent の installerが httpd.conf を書き換える為に実施
$ chown -R apache:apache /etc/httpd/conf

$ su - apache
$ cd /etc/httpd/openam
$ wget https://github.com/OpenIdentityPlatform/OpenAM-Web-Agents/releases/download/4.1.1/Apache_v24_Linux_64bit_4.1.1.zip
$ unzip Apache_v24_Linux_64bit_4.1.1.zip

$ echo "ないしょ" > web_agents_passwd

$ cd /etc/httpd/openam/web_agents/apache24_agent/bin
$ ./agentadmin --help
$ ./agentadmin --i   ## 「i」は、インストールを意味します

  Configuration file [/opt/apache/conf/httpd.conf]: /etc/httpd/conf/httpd.conf
  Existing OpenSSOAgentBootstrap.properties file: NULL
  OpenAM server URL: https://sp.end0tknr.com/sso
  Agent URL: https://sp-agt.end0tknr.com
  Agent Profile name: idp_to_sp_by_saml
  Agent realm/organization name: [/]: NULL
  The path and name of the password file: /etc/httpd/openam/web_agents_passwd


$ sudo /etc/init.d/httpd stop
$ sudo /etc/init.d/httpd start

2. SAML設定

ここからが、本題の SAML設定です

2.1. SAMLトラストサークル作成

OpenAM Service Provider へブラウザで amadminでログインし 「FEDERATION」→「連携タブ」→「新規」ボタンをクリックして下さい。 「トラストサークルの作成」画面にて「名前」【のみ】入力し、 「了解」をクリックすることで 【空】のトラストサークルを作成します。

f:id:end0tknr:20200822100955p:plain

f:id:end0tknr:20200822101008p:plain

f:id:end0tknr:20200822101017p:plain

2.2. SAMLトラストサークルへのエンティティ追加 (ServiceProvider)

「Top Level Realm」→「Create SAMLv2 Providers」→ 「Create Hosted Service Provider」で遷移後、 「このサーバー上に SAMLv2 サービスプロバイダを作成します」画面で、 以下に記載のように設定することで、 先程作成したトラストサークルへServiceProviderが追加されます。

f:id:end0tknr:20200822101924p:plain

f:id:end0tknr:20200822101935p:plain

項目 内容
このプロバイダのメタデータはありますか? いいえ
メタデータURL https://sp.end0tknr.com:443/sso
トラストサークル 既存のトラストサークルに追加します
既存のトラストサークル ★先程作成したトラストサークル名を選択下さい
IdPのデフォルト属性マッピングを使用しますか はい

2.3.SAMLトラストサークルへのエンティティ追加 (Identity Provider)

「Top Level Realm」→「Create SAMLv2 Providers」→ 「Register Remote Identity Provider」で遷移後、 「SAMLv2 リモートアイデンティティープロバイダを作成します」画面で、 以下に記載のように設定することで、 先程作成したトラストサークルへIdentity Providerが追加されます。

f:id:end0tknr:20200822103147p:plain

f:id:end0tknr:20200822103201p:plain

項目 内容
メタデータ ファイルはどこに存在しますか? URL
メタデータが配置されているURL https://idp.end0tknr.com/sso/saml2/jsp/exportmetadata.jsp?realm=/
トラストサークル 既存のトラストサークルに追加します
既存のトラストサークル ★先程作成したトラストサークル名を選択下さい

2.4. SAMLトラストサークル作成と、エンティティ追加(IdP側)

先程までに行ったSP側と、ほぼ同様の手順で、IdP側(idp.end0tknr.com)へも SAMLトラストサークル作成と、エンティティ追加を行って下さい。 IdP側のトラストサークルに追加するServiceProviderエンティティのメタデータURLは、 例えば、次のものです。

https://sp.end0tknr.com/sso/saml2/jsp/exportmetadata.jsp?realm=/

2.5. SAMLエンティティ(ServiceProvider)の修正 表明処理における自動連携設定

ここでは、IdPとSP間での、ユーザID(uid)の連携を設定します。

まず、SAMLトラストサークル設定画面にあるエンティティプロバイダ(SP)のリンクから、 設定画面へ遷移して下さい。

f:id:end0tknr:20200822104246p:plain

次に「SP」の「表明処理」画面で以下のように設定し、完了です。

項目 内容
自動連携 有効 はい
自動連携 属性 uid

f:id:end0tknr:20200822104832p:plain

2.6. WebAgent設定

SP側 WebAgentへのアクセスで、IdP側ログイン画面へリダイレクトする設定を行います。 まず、「Top Level Realm」→「Agents」で画面遷移して下さい。

f:id:end0tknr:20200822110230p:plain

f:id:end0tknr:20200822110241p:plain

「グローバル」タブにて以下の赤線のように設定を行って下さい。

項目 内容
エージェント通知 URL https://idp.end0tknr.com:443/UpdateAgentCacheServlet?shortcircuit=false
エージェント通知 URI プレフィックス https://idp.end0tknr.com:443/amagent
CDSSO のエージェントルート URL https://idp.end0tknr.com:443/
完全修飾ドメイン名 FQDN デフォルト end0tknr.com

f:id:end0tknr:20200822111432p:plain

f:id:end0tknr:20200822112127p:plain

「OpenAMサービス」タブタブにて以下の赤線のような設定を【戦闘に】追加して下さい。

項目 内容
OpenAM ログイン URL https://sp.end0tknr.com/sso/saml2/jsp/spSSOInit.jsp?metaAlias=/sp&idpEntityID=https://idp.end0tknr.com:443/sso
OpenAM ログアウト URL https://sp.end0tknr.com/sso/UI/Logout?realm=/

f:id:end0tknr:20200822113353p:plain

以上で、設定完了で、SP側WebAgentへブラウザでアクセスすると、IdPのログイン画面が表示され、 認証成功すると、SP側WebAgent の画面が表示されるかと思います。