DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Uncategorized»Rendez votre politique de sécurité auditable
    Uncategorized

    Rendez votre politique de sécurité auditable

    mars 3, 2023
    Rendez votre politique de sécurité auditable
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    La semaine dernière, j’ai écrit sur le fait de mettre la bonne fonctionnalité au bon endroit. J’ai utilisé la limitation de débit comme exemple, en la déplaçant d’une bibliothèque à l’intérieur de l’application vers la passerelle API. Aujourd’hui, je vais utiliser un autre exemple : l’authentification et l’autorisation.

    Sécuriser une application Spring Boot

    Je continuerai à utiliser Spring Boot dans ce qui suit car je le connais bien. L’application Spring Boot propose un point de terminaison REST pour vérifier les salaires des employés.

    Le cas d’utilisation spécifique est tiré du site Open Policy Agent (plus tard):

    Créez une politique qui permet aux utilisateurs de demander leur propre salaire ainsi que le salaire de leurs subordonnés directs.

    Nous avons besoin d’un moyen de :

    1. Authentifiez une requête HTTP comme provenant d’un utilisateur connu.
    2. Vérifiez si l’utilisateur a accès aux données salariales.

    Dans tous les autres cas, renvoyez un 401.

    Je vais passer un jeton d’authentification dans la demande pour simplifier les choses. Je ne m’appuierai pas sur un backend d’authentification/autorisation dédié, tel que Keycloak, mais cela devrait être une approche similaire si vous le faites.

    Pour activer Spring Security sur l’application, nous devons ajouter Spring Boot Security Starter.

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    Nous devons également permettre à Spring Security d’opérer sa magie :

    @SpringBootApplication
    @EnableWebSecurity
    class SecureBootApplication

    Une fois ces deux étapes en place, nous pouvons commencer à sécuriser l’application conformément à l’exigence ci-dessus :

    internal fun security() = beans {                                       //1
        bean {
            val http = ref<HttpSecurity>()
            http {
                authorizeRequests {
                    authorize("/finance/salary/**", authenticated)          //2
                }
                addFilterBefore<UsernamePasswordAuthenticationFilter>(
                    TokenAuthenticationFilter(ref())                        //3
                )
                httpBasic { disable() }
                csrf { disable() }
                logout { disable() }
                sessionManagement {
                    sessionCreationPolicy = SessionCreationPolicy.STATELESS
                }
            }
            http.build()
        }
        bean { TokenAuthenticationManager(ref(), ref()) }                   //4
    }

    1. Utilisez le Kotlin Beans DSL – parce que je peux.
    2. N’autorisez l’accès au point de terminaison qu’aux utilisateurs authentifiés.
    3. Ajoutez un filtre dans la chaîne de filtrage pour remplacer l’authentification standard.
    4. Ajoutez un gestionnaire d’authentification personnalisé.

    Les requêtes ressemblent à ceci :

    curl -H 'Authorization: xyz'  localhost:9080/finance/salary/bob

    Le filtre extrait de la requête les données nécessaires utilisées pour décider d’autoriser ou non la requête :

    internal class TokenAuthenticationFilter(authManager: AuthenticationManager) :
        AbstractAuthenticationProcessingFilter("/finance/salary/**", authManager) {
    
        override fun attemptAuthentication(req: HttpServletRequest, resp: HttpServletResponse): Authentication {
            val header = req.getHeader("Authorization")                   //1
            val path = req.servletPath.split("https://dzone.com/")                         //2
            val token = KeyToken(header, path)                            //3
            return authenticationManager.authenticate(token)              //4
        }
    
        // override fun successfulAuthentication(
    }

    1. Obtenez le jeton d’authentification.
    2. Obtenez le chemin.
    3. Enveloppez-le sous une structure dédiée.
    4. Essayez d’authentifier le jeton.

    À son tour, le gestionnaire essaie d’authentifier le jeton :

    internal class TokenAuthenticationManager(
        private val accountRepo: AccountRepository,
        private val employeeRepo: EmployeeRepository
    ) : AuthenticationManager {
      override fun authenticate(authentication: Authentication): Authentication {
        val token = authentication.credentials as String? ?:                       //1
            throw BadCredentialsException("No token passed")
        val account = accountRepo.findByPassword(token).orElse(null) ?:            //2
            throw BadCredentialsException("Invalid token")
        val path = authentication.details as List<String>
        val accountId = account.id
        val segment = path.last()
        if (segment == accountId) return authentication.withPrincipal(accountId)   //3
        val employee = employeeRepo.findById(segment).orElse(null)                 //4
        val managerUserName = employee?.manager?.userName
        if (managerUserName != null && managerUserName == accountId)               //5
            return authentication.withPrincipal(accountId)                         //5
        throw InsufficientAuthenticationException("Incorrect token")               //6
      }
    }

    1. Obtenez le jeton d’autorisation transmis par le filtre.
    2. Essayez de trouver le compte qui a ce jeton. Par souci de simplicité, le jeton est stocké en texte brut sans hachage.
    3. Si le compte tente d’accéder à ses données, autorisez-le.
    4. Sinon, nous devons charger la hiérarchie à partir d’un autre dépôt.
    5. Si le compte tente d’accéder aux données d’un employé qu’il gère, autorisez-le.
    6. Sinon, niez-le.

    L’ensemble du flux peut être résumé comme suit :

    Flux de sécurisation d'une application Spring Boot

    Maintenant, nous pouvons essayer quelques requêtes.

    curl -H 'Authorization: bob' localhost:9080/finance/salary/bob

    bob demande son propre salaire, et ça marche.

    curl -H 'Authorization: bob' localhost:9080/finance/salary/alice

    bob demande le salaire d’un de ses subordonnés, et ça marche aussi.

    curl -H 'Authorization: bob' localhost:9080/finance/salary/alice

    alice demande le salaire de son manager, ce qui n’est pas autorisé.

    Le code ci-dessus fonctionne parfaitement mais a un gros problème : il n’y a aucun moyen d’auditer la logique. Il faut connaître Kotlin et le fonctionnement de Spring Security pour s’assurer que la mise en œuvre est correcte.

    Présentation d’Open Policy Agent

    Open Policy Agent, ou OPA en abrégé, se décrit comme un « contrôle basé sur des politiques pour les environnements natifs du cloud ».

    Arrêtez d’utiliser un langage de politique, un modèle de politique et une API de politique différents pour chaque produit et service que vous utilisez. Utilisez OPA pour un ensemble d’outils et un cadre unifiés pour la politique sur la pile cloud native.

    Que ce soit pour un service ou pour tous vos services, utilisez OPA pour dissocier la politique du code du service afin de pouvoir publier, analyser et réviser les politiques (que les équipes de sécurité et de conformité adorent) sans sacrifier la disponibilité ou les performances.

    – Site Web de l’OPA

    En bref, OPA permet d’écrire des politiques et propose une CLI et une application démon pour les évaluer.

    Vous écrivez des politiques dans un langage interprété spécifique nommé Rego, et je dois admettre que ce n’est pas amusant. Quoi qu’il en soit, voici notre politique ci-dessus écrite en texte « clair »:

    package ch.frankel.blog.secureboot
    
    employees := data.hierarchy                                 //1
    
    default allow := false
    
    # Allow users to get their own salaries.
    allow {
        input.path == ["finance", "salary", input.user]         //2
    }
    
    # Allow managers to get their subordinates' salaries.
    allow {
        some username
        input.path = ["finance", "salary", username]            //3
        employees[input.user][_] == username                    //3
    }

    1. Obtenez la hiérarchie des employés d’une manière ou d’une autre (voir ci-dessous).
    2. Si le compte demande leur salaire, autorisez l’accès.
    3. Si le compte demande le salaire d’un subordonné, autorisez l’accès.

    J’ai utilisé deux variables dans l’extrait ci-dessus : input et data. input est la charge utile que l’application envoie à OPA. Il doit être au format JSON et se présenter sous la forme suivante :

    {
        "path": [
            "finance",
            "salary",
            "alice"
        ],
        "user": "bob"
    }

    Plus de bonté d’agent de politique ouverte

    Cependant, OPA ne peut pas décider seul de l’entrée, car il ne connaît pas la hiérarchie de l’employé. Une approche consisterait à charger les données de la hiérarchie sur l’application et à les envoyer à OPA. Une approche plus robuste consiste à laisser l’OPA accéder aux données externes pour séparer proprement les responsabilités. OPA offre de nombreuses options pour y parvenir. Ici, je fais semblant d’extraire des données de la Employee base de données, regroupez-le avec le fichier de stratégie, servez le bundle via HTTP et configurez OPA pour le charger à intervalles réguliers.

    Extrayez les données de la base de données Employee, regroupez-les avec le fichier de stratégie, servez le bundle via HTTP et configurez OPA pour le charger à intervalles réguliers.

    Notez que vous ne devez pas utiliser Apache APISIX uniquement pour servir des fichiers statiques. Mais comme je l’utiliserai dans la prochaine évolution de mon architecture, je souhaite éviter d’avoir un serveur HTTP séparé pour simplifier le système.

    Maintenant que nous avons déplacé la logique de décision vers OPA, nous pouvons remplacer notre code par une requête au service OPA. La nouvelle version du gestionnaire d’authentification est :

    internal class OpaAuthenticationManager(
        private val accountRepo: AccountRepository,
        private val opaWebClient: WebClient
    ) : AuthenticationManager {
    
        override fun authenticate(authentication: Authentication): Authentication {
            val token = authentication.credentials as String? ?:                       //1
                throw BadCredentialsException("No token passed")
            val account = accountRepo.findByPassword(token).orElse(null) ?:            //1
                throw BadCredentialsException("Invalid token")
            val path = authentication.details as List<String>
            val decision = opaWebClient.post()                                         //2
                .accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(OpaInput(DataInput(account.id, path)))                      //3
                .exchangeToMono { it.bodyToMono(DecisionOutput::class.java) }          //4
                .block() ?: DecisionOutput(ResultOutput(false))                        //5
            if (decision.result.allow) return authentication.withPrincipal(account.id) //6
            else throw InsufficientAuthenticationException("OPA disallow")             //6
        }
    }

    1. Gardez l’initiale authentification logique.
    2. Remplacez l’autorisation par un appel au service OPA.
    3. Sérialisez les données pour qu’elles soient conformes à l’entrée JSON attendue par la règle OPA.
    4. Désérialisez le résultat.
    5. Si quelque chose ne va pas,…
    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.