├── .github
├── CODEOWNERS
├── dependabot.yml
└── workflows
│ └── cd.yaml
├── .gitignore
├── .mvn
├── extensions.xml
└── maven.config
├── CONTRIBUTING.md
├── Jenkinsfile
├── LICENSE.txt
├── README.md
├── docs
├── CHANGELOG.old.md
├── README.md
└── images
│ ├── add.svg
│ ├── config-after.png
│ ├── config-before.png
│ ├── console-after.png
│ ├── console-before.png
│ ├── error.svg
│ ├── global-settings.png
│ └── information.svg
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── michelin
│ │ └── cio
│ │ └── hudson
│ │ └── plugins
│ │ ├── maskpasswords
│ │ ├── MaskPasswordsBuildWrapper.java
│ │ ├── MaskPasswordsConfig.java
│ │ ├── MaskPasswordsConsoleLogFilter.java
│ │ └── MaskPasswordsOutputStream.java
│ │ ├── passwordparam
│ │ ├── PasswordParameterDefinition.java
│ │ └── PasswordParameterValue.java
│ │ └── util
│ │ └── MaskPasswordsUtil.java
└── resources
│ ├── com
│ └── michelin
│ │ └── cio
│ │ └── hudson
│ │ └── plugins
│ │ ├── maskpasswords
│ │ ├── MaskPasswordsBuildWrapper.properties
│ │ └── MaskPasswordsBuildWrapper
│ │ │ ├── config.jelly
│ │ │ ├── config.properties
│ │ │ ├── global.jelly
│ │ │ ├── global.properties
│ │ │ ├── help-globalVarMaskEnabledGlobally.html
│ │ │ ├── help-globalVarMaskRegexes.html
│ │ │ ├── help-globalVarPasswordPairs.html
│ │ │ └── help.html
│ │ └── passwordparam
│ │ ├── PasswordParameterDefinition.properties
│ │ ├── PasswordParameterDefinition
│ │ ├── config.jelly
│ │ └── index.jelly
│ │ └── PasswordParameterValue
│ │ └── value.jelly
│ └── index.jelly
└── test
├── java
└── com
│ └── michelin
│ └── cio
│ └── hudson
│ └── plugins
│ ├── integrations
│ └── CorePasswordParameterTest.java
│ ├── maskpasswords
│ ├── MaskPasswordConfigTests.java
│ ├── MaskPasswordsURLEncodingTest.java
│ └── MaskPasswordsWorkflowTest.java
│ ├── passwordparam
│ └── PasswordParameterTest.java
│ └── util
│ └── MaskPasswordsUtilTest.java
└── resources
└── com
└── michelin
└── cio
└── hudson
└── plugins
└── util
├── echoAwsJson.txt
└── echoAwsJsonMasked.txt
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @jenkinsci/mask-passwords-plugin-developers
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuring-dependabot-version-updates
2 |
3 | version: 2
4 | updates:
5 | - package-ecosystem: maven
6 | directory: /
7 | schedule:
8 | interval: monthly
9 | - package-ecosystem: github-actions
10 | directory: /
11 | schedule:
12 | interval: monthly
13 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yaml:
--------------------------------------------------------------------------------
1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins
2 |
3 | name: cd
4 | on:
5 | workflow_dispatch:
6 | check_run:
7 | types:
8 | - completed
9 |
10 | jobs:
11 | maven-cd:
12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1
13 | secrets:
14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | work
3 |
4 | # Eclipse project files
5 | .settings
6 | .classpath
7 | .project
8 |
9 | # Intellij project files
10 | .idea
11 | *.iml
12 |
13 | **/.DS_Store
14 |
--------------------------------------------------------------------------------
/.mvn/extensions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | io.jenkins.tools.incrementals
4 | git-changelist-maven-extension
5 | 1.8
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.mvn/maven.config:
--------------------------------------------------------------------------------
1 | -Pconsume-incrementals
2 | -Pmight-produce-incrementals
3 | -Dchangelist.format=%d.v%s
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to the mask passwords plugin
2 |
3 | Thank you for your interest in contributing to the Jenkins "mask passwords plugin"! This guide will provide you with the necessary information to contribute to the plugin.
4 |
5 | ## Getting Started
6 |
7 | Before you begin contributing to the plugin, you should familiarize yourself with the plugin's source code and build process. You can find the plugin's source code on GitHub at https://github.com/jenkinsci/mask-passwords-plugin. The plugin is built using Maven, so you will need to have Maven installed on your system to build the plugin.
8 |
9 | ## Newcomers
10 |
11 | If you are a newcomer contributor and have any questions, please do not hesitate to ask in the [Newcomers Gitter channel](https://app.gitter.im/#/room/#jenkinsci_newcomer-contributors:gitter.im).
12 |
13 | ## Contributing Code
14 |
15 | If you would like to contribute code to the plugin, please follow these steps:
16 |
17 | * Fork the plugin's repository on GitHub.
18 | * Clone your forked repository to your local machine.
19 | * Make your changes to the plugin's source code.
20 | * Test your changes to ensure that they do not introduce any new issues.
21 | * Commit your changes and push them to your forked repository.
22 | * Open a pull request from your forked repository to the main repository. Please ensure that your pull request includes a clear description of the changes you have made and the reason for making them.
23 |
24 | ## Run Locally
25 |
26 | * Ensure Java 11 or 17 is available.
27 | ```console
28 | $ java -version
29 | openjdk version "11.0.18" 2023-01-17
30 | OpenJDK Runtime Environment Temurin-11.0.18+10 (build 11.0.18+10)
31 | OpenJDK 64-Bit Server VM Temurin-11.0.18+10 (build 11.0.18+10, mixed mode)
32 | ```
33 |
34 | * Ensure Maven > 3.8.4 or newer is installed and included in the PATH environment variable.
35 | ```console
36 | $ mvn --version
37 | Apache Maven 3.9.1 (2e178502fcdbffc201671fb2537d0cb4b4cc58f8)
38 | Maven home: /opt/apache-maven-3.9.1
39 | Java version: 11.0.18, vendor: Eclipse Adoptium, runtime: /opt/jdk-11
40 | Default locale: en_US, platform encoding: UTF-8
41 | OS name: "linux", version: "4.18.0-425.13.1.el8_7.x86_64", arch: "amd64", family: "unix"
42 | ```
43 |
44 | ## CLI
45 |
46 | - Use the following command
47 | ```console
48 | $ mvn hpi:run
49 | ...
50 | INFO: Jenkins is fully up and running
51 | ```
52 | - Open http://localhost:8080/jenkins/ to test the plugin locally.
53 |
54 | ## Reporting Issues
55 |
56 | If you encounter any issues with the plugin, please report them on the [Jira issue tracker](https://www.jenkins.io/participate/report-issue/redirect/#15761) . When reporting an issue, please include as much information as possible, including a description of the issue, steps to reproduce the issue, and any relevant logs or error messages. ["How to report an issue"](https://www.jenkins.io/participate/report-issue/) provides more details on the information needed for a better bug report.
57 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | /*
2 | See the documentation for more options:
3 | https://github.com/jenkins-infra/pipeline-library/
4 | */
5 | buildPlugin(
6 | forkCount: '1C', // Run parallel tests on ci.jenkins.io for lower costs, faster feedback
7 | useContainerAgent: true, // Set to `false` if you need to use Docker for containerized tests
8 | configurations: [
9 | [platform: 'linux', jdk: 21],
10 | [platform: 'windows', jdk: 17],
11 | ])
12 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2010-2011, Manufacture Francaise des Pneumatiques Michelin, Romain Seguy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mask Passwords plugin for Jenkins
2 |
3 | This plugin allows masking passwords that may appear in the console.
4 |
5 | ## About this plugin
6 |
7 | This plugin allows masking passwords that may appear in the console,
8 | including the ones defined as build parameters.
9 | This often happens, for example, when you use build steps which *can't* handle passwords properly.
10 | Take a look at the following example.
11 |
12 | ### Before
13 |
14 | Consider you're using an **Invoke Ant** build step to run an Ant target.
15 | This target requires a password to achieve its goal.
16 | You would end up having a job configuration like this:
17 |
18 | 
19 |
20 | Of course, you could have created a variable to store the password and use this variable in the build step configuration so that it doesn't appear as plain text.
21 | But you would still end with a console output like this:
22 |
23 | 
24 |
25 | ### After
26 |
27 | When activating the **Mask passwords** option in a job, the builds' **Password Parameters**
28 | (or any other type of build parameters selected for masking in **Manage Jenkins** \> **Configure System**) are automatically masked from the console.
29 | Furthermore, you can also safely define a list of static passwords to be masked
30 | (you can also define a list of static password shared by all jobs in Jenkins' main configuration screen).
31 | As such, the passwords don't appear anymore as plain text in the job configuration
32 | (plus it is ciphered in the job configuration file):
33 |
34 | 
35 |
36 | Once done, new builds will have the passwords masked from the console output:
37 |
38 | 
39 |
40 | ## User guide
41 |
42 | First, go to Jenkins' main configuration screen (**Manage Jenkins** \> **Configure System**) and select,
43 | in the **Mask Passwords - Configuration** section, which kind of build parameters have to be automatically masked from the console output:
44 |
45 | 
46 |
47 | Notice that, as of version 2.7, you can also define global passwords (defined as pairs of name/password) that can be accessed across all jobs.
48 |
49 | Then, for a specific job, activate the **Mask passwords** option in the **Build Environment** section to mask passwords from the console:
50 |
51 | 1. All the password parameters defined for the job will be automatically hidden.
52 | 2. For each other kind of password (that is, static ones) that may appear in the console output,
53 | add an entry (by clicking on the **Add** button) and set the **Password** field.
54 | You may additionally set the **Name** field.
55 | If you do so, the password will then be available as a standard variable.
56 | It is then possible to refer to this password using this variable rather than keying it in a field which is not ciphered.
57 | Take a look at the screenshots above for an example.
58 |
59 | ## Release Notes
60 |
61 | * See [GitHub Releases](https://github.com/jenkinsci/mask-passwords-plugin/releases) for recent releases
62 | * See the [Changelog archive](./docs/CHANGELOG.old.md) for 2.12.0 and before
63 |
--------------------------------------------------------------------------------
/docs/CHANGELOG.old.md:
--------------------------------------------------------------------------------
1 | # Version history (archive)
2 |
3 | ## New releases
4 |
5 | See [GitHub Releases](https://github.com/jenkinsci/mask-passwords-plugin/releases)
6 |
7 | ## Version 2.12.0
8 |
9 | Release Date: (Jun 01, 2018)
10 |
11 | - [ PR
12 | \#18](https://github.com/jenkinsci/mask-passwords-plugin/pull/18) -
13 | Mask Passwords Console Log filter can be now applied to all Run
14 | types
15 | - It should allow filtering Pipeline jobs
16 | once [JENKINS-45693](http://issues.jenkins-ci.org/browse/JENKINS-45693)
17 | is implemented
18 | -  Update
19 | minimal core requirement to 1.625.3
20 |
21 | ## Version 2.11.0
22 |
23 | Release Date: (Mar 13, 2018)
24 |
25 | -  Update
26 | minimal core requirement to 1.625.3
27 | -  Developer:
28 | Update Plugin POm to the latest version
29 |
30 | ## Version 2.10.1
31 |
32 | Release Date: (Apr 11, 2017)
33 |
34 | -  Prevent
35 | NullPointerException when loading configurations from the disk
36 | ([JENKINS-43504](https://issues.jenkins-ci.org/browse/JENKINS-43504))
37 |
38 | ## Version 2.10
39 |
40 | Release Date: (Apr 08, 2017)
41 |
42 | -  Rework
43 | the Parameter Definition processing engine, improve the reliability
44 | of Sensitive parameter discovery
45 | -  Fix
46 | a number of issues with parameter masking reported to the plugin.
47 | Full list will be published later
48 |
49 | ## Version 2.9
50 |
51 | Release Date: (30/11/2016)
52 |
53 | - 
54 | Add option to mask output strings by a regular expression, also with
55 | a global setting ([PR
56 | \#6](https://github.com/jenkinsci/mask-passwords-plugin/pull/6))
57 | - 
58 | Properly invoke flush/close operations for the logger in
59 | MaskPasswordOutputStream ([PR
60 | \#8](https://github.com/jenkinsci/mask-passwords-plugin/pull/8))
61 | - 
62 | Fix issues reported by FindBugs
63 | - 
64 | Update to the new Parent POM
65 |
66 | ## Version 2.8
67 |
68 | Release Date: (18/10/2015)
69 |
70 | - 
71 | Implement SimpleBuildWrapper in order to support Workflow project
72 | type
73 | ([JENKINS-27392](https://issues.jenkins-ci.org/browse/JENKINS-27392))
74 |
75 | ## Version 2.7.4
76 |
77 | Release Date: (29/07/2015)
78 |
79 | - 
80 | Password parameters were insensitive
81 | - 
82 | "Mask passwords" build wrapper was generating insensitive
83 | environment variables
84 |
85 | Fixed issues (to be investigated and updated):
86 |
87 | - Masking of global password parameters in EnvInject
88 | ([JENKINS-25821](https://issues.jenkins-ci.org/browse/JENKINS-25821))
89 | - Masked Passwords are shown as input parameters in Build pipeline
90 | plugin
91 | ([JENKINS-16516](https://issues.jenkins-ci.org/browse/JENKINS-16516))
92 |
93 | ## Version 2.7.3
94 |
95 | Release Date: (29/04/2015)
96 |
97 | - Fixed
98 | [JENKINS-12161](https://issues.jenkins-ci.org/browse/JENKINS-12161):
99 | EnvInject vars could have been not masked because of plugins loading order
100 | - Fixed
101 | [JENKINS-14687](https://issues.jenkins-ci.org/browse/JENKINS-14687):
102 | password exposed unencrypted in HTML source
103 |
104 | ## Version 2.7.2
105 |
106 | Release Date: (12/07/2011)
107 |
108 | - Fixed
109 | [JENKINS-11934](https://issues.jenkins-ci.org/browse/JENKINS-11934):
110 | Once a job config was submitted, new/updated global passwords were
111 | not masked
112 | - Implemented
113 | [JENKINS-11924](https://issues.jenkins-ci.org/browse/JENKINS-11924):
114 | Improved global passwords-related labels
115 |
116 | ## Version 2.7.1
117 |
118 | Release Date: (10/27/2011)
119 |
120 | - Fixed
121 | [JENKINS-11514](https://issues.jenkins-ci.org/browse/JENKINS-11514):
122 | When migrating from an older version of the plugin,
123 | `NullPointerException`s were preventing the jobs using Mask
124 | Passwords to load
125 | - Fixed
126 | [JENKINS-11515](https://issues.jenkins-ci.org/browse/JENKINS-11515):
127 | Mask Passwords global config was not actually saved when no global
128 | passwords were defined
129 |
130 | ## Version 2.7
131 |
132 | Release Date: (10/20/2011)
133 |
134 | - Implemented
135 | [JENKINS-11399](https://issues.jenkins-ci.org/browse/JENKINS-11399):
136 | It is now possible to define name/password pairs in Jenkins' main
137 | configuration screen (**Manage Jenkins** \> **Configure System**)
138 |
139 | ## Version 2.6.1
140 |
141 | Release Date: (05/26/2011)
142 |
143 | - Fixed a bug which was emptying the console output if there was no
144 | password to actually mask
145 |
146 | ## Version 2.6
147 |
148 | Release Date: (04/29/2011)
149 |
150 | - Added a new type of build parameter: **Non-Stored Password
151 | Parameter**
152 | - Blank passwords are no more masked, avoiding overcrowding the
153 | console with stars
154 |
155 | ## Version 2.5
156 |
157 | Release Date: (03/11/2011)
158 |
159 | - New configuration screen (in **Manage Jenkins** \> **Configure
160 | System**) allowing to select which build parameters have to be
161 | masked (**Password Parameter** are selected by default)
162 | - Fixed a bug which was preventing to mask passwords containing
163 | regular expressions' meta-characters or escape sequences
164 |
165 | ## Version 2.0
166 |
167 | Release Date: (02/23/2011)
168 |
169 | - Builds' **Password Parameter**s are now automatically masked.
170 |
171 | ## Version 1.0
172 |
173 | Release Date: (09/01/2010)
174 |
175 | - Initial release
176 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | This plugin allows masking passwords that may appear in the console
2 |
3 | The current version of this plugin may not be safe to use. Please review
4 | the following warnings before use:
5 |
6 | - [Plain text passwords shown in global configuration form
7 | fields](https://jenkins.io/security/advisory/2019-08-07/#SECURITY-157)
8 |
9 | **This plugin is up for adoption.** Want to help improve this plugin?
10 | [Click here to learn
11 | more](https://wiki.jenkins.io/display/JENKINS/Adopt+a+Plugin "Adopt a Plugin")!
12 |
13 | # About this plugin
14 |
15 | This plugin allows masking passwords that may appear in the console,
16 | including the ones defined as build parameters. This often happens, for
17 | example, when you use build steps which *can't* handle passwords
18 | properly. Take a look at the following example.
19 |
20 | ## Before
21 |
22 | Consider you're using an **Invoke Ant** build step to run an Ant target.
23 | This target requires a password to achieve its goal. You would end up
24 | having a job configuration like this:
25 |
26 | 
27 |
28 | Of course, you could have created a variable to store the password and
29 | use this variable in the build step configuration so that it doesn't
30 | appear as plain text. But you would still end with a console output like
31 | this:
32 |
33 | 
34 |
35 | ## After
36 |
37 | When activating the **Mask passwords** option in a job, the builds'
38 | **Password Parameters** (or any other type of build parameters selected
39 | for masking in **Manage Jenkins** \> **Configure System**) are
40 | automatically masked from the console. Furthermore, you can also safely
41 | define a list of static passwords to be masked (you can also define a
42 | list of static password shared by all jobs in Jenkins' main
43 | configuration screen). As such, the passwords don't appear anymore as
44 | plain text in the job configuration (plus it is ciphered in the job
45 | configuration file):
46 |
47 | 
48 |
49 | Once done, new builds will have the passwords masked from the console
50 | output:
51 |
52 | 
53 |
54 | # User guide
55 |
56 | First, go to Jenkins' main configuration screen (**Manage Jenkins** \>
57 | **Configure System**) and select, in the **Mask Passwords -
58 | Configuration** section, which kind of build parameters have to be
59 | automatically masked from the console output:
60 |
61 | 
62 |
63 | Notice that, as of version 2.7, you can also define global passwords
64 | (defined as pairs of name/password) that can be accessed across all
65 | jobs.
66 |
67 | Then, for a specific job, activate the **Mask passwords** option in the
68 | **Build Environment** section to mask passwords from the console:
69 |
70 | 1. All the password parameters defined for the job will be
71 | automatically hidden.
72 | 2. For each other kind of password (that is, static ones) that may
73 | appear in the console output, add an entry (by clicking on the
74 | **Add** button) and set the **Password** field.
75 | You may additionally set the **Name** field. If you do so, the
76 | password will then be available as a standard variable. It is then
77 | possible to refer to this password using this variable rather than
78 | keying it in a field which is not ciphered. Take a look at the
79 | screenshots above for an example.
80 |
81 |
--------------------------------------------------------------------------------
/docs/images/add.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/images/config-after.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/mask-passwords-plugin/900cdfb39bc23a49f952ae5202bdd66f5f474ead/docs/images/config-after.png
--------------------------------------------------------------------------------
/docs/images/config-before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/mask-passwords-plugin/900cdfb39bc23a49f952ae5202bdd66f5f474ead/docs/images/config-before.png
--------------------------------------------------------------------------------
/docs/images/console-after.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/mask-passwords-plugin/900cdfb39bc23a49f952ae5202bdd66f5f474ead/docs/images/console-after.png
--------------------------------------------------------------------------------
/docs/images/console-before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/mask-passwords-plugin/900cdfb39bc23a49f952ae5202bdd66f5f474ead/docs/images/console-before.png
--------------------------------------------------------------------------------
/docs/images/error.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/images/global-settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/mask-passwords-plugin/900cdfb39bc23a49f952ae5202bdd66f5f474ead/docs/images/global-settings.png
--------------------------------------------------------------------------------
/docs/images/information.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | org.jenkins-ci.plugins
6 | plugin
7 | 5.17
8 |
9 |
10 |
11 | mask-passwords
12 | hpi
13 | Mask Passwords Plugin
14 | Masks passwords that may appear in the console. Provides non-stored password parameter.
15 | https://github.com/jenkinsci/mask-passwords-plugin
16 | ${changelist}
17 |
18 |
19 | 2.479
20 | ${jenkins.baseline}.3
21 | Max
22 | 999999-SNAPSHOT
23 | jenkinsci/mask-passwords-plugin
24 | true
25 |
26 |
27 |
28 | scm:git:https://github.com/${gitHubRepo}.git
29 | scm:git:git@github.com:${gitHubRepo}.git
30 | https://github.com/${gitHubRepo}
31 | ${scmTag}
32 |
33 |
34 |
35 |
36 | MIT License
37 | https://opensource.org/licenses/MIT
38 |
39 |
40 |
41 |
42 |
43 | repo.jenkins-ci.org
44 | https://repo.jenkins-ci.org/public/
45 |
46 |
47 |
48 |
49 |
50 | repo.jenkins-ci.org
51 | https://repo.jenkins-ci.org/public/
52 |
53 |
54 |
55 |
56 |
57 |
58 | io.jenkins.tools.bom
59 | bom-${jenkins.baseline}.x
60 | 4845.v9163d3278e4f
61 | import
62 | pom
63 |
64 |
65 |
66 |
67 |
68 |
69 | org.jenkins-ci.plugins.workflow
70 | workflow-job
71 | test
72 |
73 |
74 | org.jenkins-ci.plugins
75 | structs
76 |
77 |
78 | org.jenkins-ci.plugins.workflow
79 | workflow-basic-steps
80 | test
81 |
82 |
83 | org.jenkins-ci.plugins.workflow
84 | workflow-cps
85 | test
86 |
87 |
88 | org.jenkins-ci.plugins.workflow
89 | workflow-durable-task-step
90 | test
91 |
92 |
93 | org.jenkins-ci.plugins.workflow
94 | workflow-step-api
95 | tests
96 | test
97 |
98 |
99 | org.jenkins-ci.plugins.workflow
100 | workflow-support
101 | tests
102 | test
103 |
104 |
105 | org.awaitility
106 | awaitility
107 | 4.3.0
108 | test
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/src/main/java/com/michelin/cio/hudson/plugins/maskpasswords/MaskPasswordsBuildWrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2010-2012, Manufacture Francaise des Pneumatiques Michelin,
5 | * Romain Seguy
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | package com.michelin.cio.hudson.plugins.maskpasswords;
27 |
28 | import com.michelin.cio.hudson.plugins.maskpasswords.MaskPasswordsConfig.VarMaskRegexEntry;
29 | import com.thoughtworks.xstream.converters.Converter;
30 | import com.thoughtworks.xstream.converters.MarshallingContext;
31 | import com.thoughtworks.xstream.converters.UnmarshallingContext;
32 | import com.thoughtworks.xstream.io.HierarchicalStreamReader;
33 | import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
34 | import edu.umd.cs.findbugs.annotations.CheckForNull;
35 | import edu.umd.cs.findbugs.annotations.NonNull;
36 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
37 | import hudson.EnvVars;
38 | import hudson.Extension;
39 | import hudson.console.ConsoleLogFilter;
40 | import hudson.model.AbstractBuild;
41 | import hudson.model.AbstractDescribableImpl;
42 | import hudson.model.AbstractProject;
43 | import hudson.model.Descriptor;
44 | import hudson.model.ParameterValue;
45 | import hudson.model.ParametersAction;
46 | import hudson.model.Run;
47 | import hudson.model.TaskListener;
48 | import hudson.tasks.BuildWrapperDescriptor;
49 | import hudson.util.Secret;
50 | import jenkins.tasks.SimpleBuildWrapper;
51 | import net.sf.json.JSONArray;
52 | import net.sf.json.JSONObject;
53 | import org.apache.commons.lang.StringUtils;
54 | import org.jenkinsci.Symbol;
55 | import org.jenkinsci.plugins.structs.describable.CustomDescribableModel;
56 | import org.jenkinsci.plugins.structs.describable.UninstantiatedDescribable;
57 | import org.jvnet.localizer.Localizable;
58 | import org.jvnet.localizer.ResourceBundleHolder;
59 | import org.kohsuke.stapler.DataBoundConstructor;
60 | import org.kohsuke.stapler.StaplerRequest2;
61 |
62 | import java.io.IOException;
63 | import java.io.OutputStream;
64 | import java.io.Serializable;
65 | import java.util.ArrayList;
66 | import java.util.HashMap;
67 | import java.util.List;
68 | import java.util.Map;
69 | import java.util.Objects;
70 | import java.util.Set;
71 | import java.util.TreeMap;
72 | import java.util.logging.Level;
73 | import java.util.logging.Logger;
74 |
75 | /**
76 | * Build wrapper that alters the console so that passwords don't get displayed.
77 | *
78 | * @author Romain Seguy (http://openromain.blogspot.com)
79 | */
80 | public final class MaskPasswordsBuildWrapper extends SimpleBuildWrapper {
81 |
82 | private final List varPasswordPairs;
83 | private final List varMaskRegexes;
84 |
85 | @DataBoundConstructor
86 | public MaskPasswordsBuildWrapper(List varPasswordPairs, List varMaskRegexes) {
87 | this.varPasswordPairs = varPasswordPairs;
88 | this.varMaskRegexes = varMaskRegexes;
89 | }
90 |
91 | public MaskPasswordsBuildWrapper(List varPasswordPairs) {
92 | this.varPasswordPairs = varPasswordPairs;
93 | this.varMaskRegexes = new ArrayList<>();
94 | }
95 |
96 | @Override
97 | public ConsoleLogFilter createLoggerDecorator(Run, ?> build) {
98 | List allPasswords = new ArrayList<>(); // all passwords to be masked
99 | List allRegexes = new ArrayList<>(); // all regexes to be masked
100 | MaskPasswordsConfig config = MaskPasswordsConfig.getInstance();
101 |
102 | // global passwords
103 | List globalVarPasswordPairs = config.getGlobalVarPasswordPairs();
104 | for(VarPasswordPair globalVarPasswordPair: globalVarPasswordPairs) {
105 | allPasswords.add(globalVarPasswordPair.getPlainTextPassword());
106 | }
107 |
108 | // global regexes
109 | List globalVarMaskRegexes = config.getGlobalVarMaskRegexesU();
110 | for(MaskPasswordsConfig.VarMaskRegexEntry globalVarMaskRegex: globalVarMaskRegexes) {
111 | allRegexes.add(globalVarMaskRegex.getValue());
112 | }
113 |
114 | // job's passwords
115 | if(varPasswordPairs != null) {
116 | for(VarPasswordPair varPasswordPair: varPasswordPairs) {
117 | String password = varPasswordPair.getPlainTextPassword();
118 | if(StringUtils.isNotBlank(password)) {
119 | allPasswords.add(password);
120 | }
121 | }
122 | }
123 |
124 | // job's regexes
125 | if(varMaskRegexes != null) {
126 | for(VarMaskRegexEntry entry: varMaskRegexes) {
127 | String regex = entry.getRegexString();
128 | if(StringUtils.isNotBlank(regex)) {
129 | allRegexes.add(regex);
130 | }
131 | }
132 | }
133 |
134 | // find build parameters which are passwords (PasswordParameterValue)
135 | ParametersAction params = build.getAction(ParametersAction.class);
136 | if(params != null) {
137 | for(ParameterValue param : params) {
138 | if(config.isMasked(param, param.getClass().getName())) {
139 | EnvVars env = new EnvVars();
140 | param.buildEnvironment(build, env);
141 | String password = env.get(param.getName());
142 | if(StringUtils.isNotBlank(password)) {
143 | allPasswords.add(password);
144 | }
145 | }
146 | }
147 | }
148 |
149 | return new FilterImpl(allPasswords, allRegexes);
150 | }
151 |
152 | @Override
153 | public boolean requiresWorkspace() {
154 | return false;
155 | }
156 |
157 | private static final class FilterImpl extends ConsoleLogFilter implements Serializable {
158 |
159 | private static final long serialVersionUID = 1L;
160 |
161 | private final List allPasswords;
162 | private final List allRegexes;
163 |
164 | FilterImpl(List allPasswords, List allRegexes) {
165 | this.allPasswords = new ArrayList<>();
166 | this.allRegexes = new ArrayList<>();
167 | for (String password : allPasswords) {
168 | this.allPasswords.add(Secret.fromString(password));
169 | }
170 | this.allRegexes.addAll(allRegexes);
171 | }
172 |
173 | @Override
174 | public OutputStream decorateLogger(Run run, OutputStream logger) {
175 | List passwords = new ArrayList<>();
176 | for (Secret password : allPasswords) {
177 | passwords.add(password.getPlainText());
178 | }
179 | List regexes = new ArrayList<>(allRegexes);
180 | String runName = run != null ? run.getFullDisplayName() : "";
181 | return new MaskPasswordsOutputStream(logger, passwords, regexes, runName);
182 | }
183 |
184 | }
185 |
186 | /**
187 | * Contributes the passwords defined by the user as variables that can be reused
188 | * from build steps (and other places).
189 | */
190 | @Override
191 | public void makeBuildVariables(AbstractBuild build, Map variables) {
192 | // global var/password pairs
193 | MaskPasswordsConfig config = MaskPasswordsConfig.getInstance();
194 | List globalVarPasswordPairs = config.getGlobalVarPasswordPairs();
195 | // we can't use variables.putAll() since passwords are ciphered when in varPasswordPairs
196 | for(VarPasswordPair globalVarPasswordPair: globalVarPasswordPairs) {
197 | variables.put(globalVarPasswordPair.getVar(), globalVarPasswordPair.getPlainTextPassword());
198 | }
199 |
200 | // job's var/password pairs
201 | if(varPasswordPairs != null) {
202 | // cf. comment above
203 | for(VarPasswordPair varPasswordPair: varPasswordPairs) {
204 | if(StringUtils.isNotBlank(varPasswordPair.getVar())) {
205 | variables.put(varPasswordPair.getVar(), varPasswordPair.getPlainTextPassword());
206 | }
207 | }
208 | }
209 | }
210 |
211 | @Override
212 | public void makeSensitiveBuildVariables(AbstractBuild build, Set sensitiveVariables) {
213 | final Map variables = new TreeMap<>();
214 | makeBuildVariables(build, variables);
215 | sensitiveVariables.addAll(variables.keySet());
216 | }
217 |
218 | @Override
219 | public void setUp(Context context, Run, ?> build, TaskListener listener, EnvVars initialEnvironment) throws IOException, InterruptedException {
220 | // nothing to do here
221 | }
222 |
223 | public List getVarPasswordPairs() {
224 | return varPasswordPairs;
225 | }
226 |
227 | public List getVarMaskRegexes() {
228 | return varMaskRegexes;
229 | }
230 |
231 | /**
232 | * Represents name/password entries defined by users in their jobs.
233 | * Equality and hashcode are based on {@code var} only, not {@code password}.
234 | * If the class gets extended, a clone() method must be implemented without super.clone() calls.
235 | */
236 | public static class VarPasswordPair extends AbstractDescribableImpl implements Cloneable {
237 |
238 | private final String var;
239 | private final Secret password;
240 |
241 | @DataBoundConstructor
242 | public VarPasswordPair(String var, Secret password) {
243 | this.var = var;
244 | this.password = password;
245 | }
246 |
247 | @Override
248 | @SuppressFBWarnings(value = "CN_IDIOM_NO_SUPER_CALL", justification = "We do not expect anybody to use this class."
249 | + "If they do, they must override clone() as well")
250 | public Object clone() {
251 | return new VarPasswordPair(getVar(), password);
252 | }
253 |
254 | @Override
255 | public boolean equals(Object obj) {
256 | if(obj == null) {
257 | return false;
258 | }
259 | if(getClass() != obj.getClass()) {
260 | return false;
261 | }
262 | final VarPasswordPair other = (VarPasswordPair) obj;
263 | return Objects.equals(this.var, other.var);
264 | }
265 |
266 | public String getVar() {
267 | return var;
268 | }
269 |
270 | public Secret getPassword() {
271 | return password;
272 | }
273 |
274 | public String getPlainTextPassword() {
275 | if (password == null || StringUtils.isBlank(password.getPlainText())) {
276 | return null;
277 | }
278 |
279 | return password.getPlainText();
280 | }
281 |
282 | @Override
283 | public int hashCode() {
284 | int hash = 3;
285 | hash = 67 * hash + (this.var != null ? this.var.hashCode() : 0);
286 | return hash;
287 | }
288 |
289 | @Extension
290 | /**
291 | * {@link CustomDescribableModel} is needed because pipeline doesn't natively support the {@link Secret} class
292 | * but we need Secret so that data-binding works correctly.
293 | */
294 | public static class DescriptorImpl extends Descriptor implements CustomDescribableModel {
295 | @NonNull
296 | @Override
297 | public UninstantiatedDescribable customUninstantiate(@NonNull UninstantiatedDescribable step) {
298 | Map arguments = step.getArguments();
299 | Map newMap1 = new HashMap<>();
300 | newMap1.put("var", arguments.get("var"));
301 | newMap1.put("password", ((Secret) arguments.get("password")).getPlainText());
302 | return step.withArguments(newMap1);
303 | }
304 |
305 | @NonNull
306 | @Override
307 | public Map customInstantiate(@NonNull Map arguments) {
308 | Map newMap = new HashMap<>();
309 | newMap.put("var", arguments.get("var"));
310 | Object password = arguments.get("password");
311 | if (password instanceof String) {
312 | password = Secret.fromString((String) password);
313 | }
314 | newMap.put("password", password);
315 | return newMap;
316 | }
317 | }
318 |
319 | }
320 |
321 | /**
322 | * Represents regexes defined by users in their jobs.
323 | * If the class gets extended, a clone() method must be implemented without super.clone() calls.
324 | */
325 | public static class VarMaskRegex extends AbstractDescribableImpl implements Cloneable {
326 |
327 | private final String regex;
328 |
329 | @DataBoundConstructor
330 | public VarMaskRegex(String regex) {
331 | this.regex = regex;
332 | }
333 |
334 | @Override
335 | @SuppressFBWarnings(value = "CN_IDIOM_NO_SUPER_CALL", justification = "We do not expect anybody to use this class."
336 | + "If they do, they must override clone() as well")
337 | public Object clone() {
338 | return new VarMaskRegex(getRegex());
339 | }
340 |
341 | @Override
342 | public boolean equals(Object obj) {
343 | if(obj == null) {
344 | return false;
345 | }
346 | if(getClass() != obj.getClass()) {
347 | return false;
348 | }
349 | final VarMaskRegex other = (VarMaskRegex) obj;
350 | return Objects.equals(this.regex, other.regex);
351 | }
352 |
353 | @CheckForNull
354 | public String getRegex() {
355 | return regex;
356 | }
357 |
358 | @Override
359 | public int hashCode() {
360 | int hash = 3;
361 | hash = 67 * hash + (this.regex != null ? this.regex.hashCode() : 0);
362 | return hash;
363 | }
364 |
365 | public String toString() {
366 | return regex;
367 | }
368 |
369 | @Extension
370 | public static class DescriptorImpl extends Descriptor {}
371 |
372 | }
373 |
374 | @Symbol("maskPasswords")
375 | @Extension(ordinal = 100) // JENKINS-12161, was previously 1000 but that made the system configuration page look weird
376 | public static final class DescriptorImpl extends BuildWrapperDescriptor {
377 |
378 | public DescriptorImpl() {
379 | super(MaskPasswordsBuildWrapper.class);
380 | }
381 |
382 | /**
383 | * @since 2.5
384 | */
385 | @Override
386 | public boolean configure(StaplerRequest2 req, JSONObject json) throws FormException {
387 | try {
388 | getConfig().clear();
389 |
390 | LOGGER.fine("Processing the maskedParamDefs and selectedMaskedParamDefs JSON objects");
391 | JSONObject submittedForm = req.getSubmittedForm();
392 |
393 | // parameter definitions to be automatically masked
394 | JSONArray paramDefinitions = submittedForm.getJSONArray("maskedParamDefs");
395 | JSONArray selectedParamDefinitions = submittedForm.getJSONArray("selectedMaskedParamDefs");
396 | for(int i = 0; i < selectedParamDefinitions.size(); i++) {
397 | if(selectedParamDefinitions.getBoolean(i)) {
398 | getConfig().addMaskedPasswordParameterDefinition(paramDefinitions.getString(i));
399 | }
400 | }
401 |
402 | // global var/password pairs
403 | if(submittedForm.has("globalVarPasswordPairs")) {
404 | Object o = submittedForm.get("globalVarPasswordPairs");
405 |
406 | if(o instanceof JSONArray) {
407 | JSONArray jsonArray = submittedForm.getJSONArray("globalVarPasswordPairs");
408 | for(int i = 0; i < jsonArray.size(); i++) {
409 | getConfig().addGlobalVarPasswordPair(new VarPasswordPair(
410 | jsonArray.getJSONObject(i).getString("var"),
411 | Secret.fromString(jsonArray.getJSONObject(i).getString("password"))));
412 | }
413 | }
414 | else if(o instanceof JSONObject) {
415 | JSONObject jsonObject = submittedForm.getJSONObject("globalVarPasswordPairs");
416 | getConfig().addGlobalVarPasswordPair(new VarPasswordPair(
417 | jsonObject.getString("var"),
418 | Secret.fromString(jsonObject.getString("password"))));
419 | }
420 | }
421 |
422 | // global regexes
423 | if(submittedForm.has("globalVarMaskRegexesU")) {
424 | Object o = submittedForm.get("globalVarMaskRegexesU");
425 |
426 | if(o instanceof JSONArray) {
427 | JSONArray jsonArray = submittedForm.getJSONArray("globalVarMaskRegexesU");
428 | for(int i = 0; i < jsonArray.size(); i++) {
429 | getConfig().addGlobalVarMaskRegex(
430 | jsonArray.getJSONObject(i).getString("key"),
431 | new VarMaskRegex(jsonArray.getJSONObject(i).getString("value")));
432 | }
433 | }
434 | else if(o instanceof JSONObject) {
435 | JSONObject jsonObject = submittedForm.getJSONObject("globalVarMaskRegexesU");
436 | getConfig().addGlobalVarMaskRegex(
437 | jsonObject.getString("key"),
438 | new VarMaskRegex(jsonObject.getString("value")));
439 | }
440 | }
441 |
442 | // global enable
443 | if(submittedForm.has("globalVarMaskEnabledGlobally")) {
444 | boolean b = submittedForm.getBoolean("globalVarMaskEnabledGlobally");
445 | if(b) {
446 | getConfig().setGlobalVarEnabledGlobally(true);
447 | }
448 | }
449 |
450 | MaskPasswordsConfig.save(getConfig());
451 |
452 | return true;
453 | }
454 | catch (Exception e) {
455 | LOGGER.log(Level.SEVERE, "Failed to save Mask Passwords plugin configuration", e);
456 | return false;
457 | }
458 | }
459 |
460 | public List getGlobalVarPasswordPairs() {
461 | return getConfig().getGlobalVarPasswordPairs();
462 | }
463 |
464 | public List getGlobalVarMaskRegexesU() {
465 | return getConfig().getGlobalVarMaskRegexesU();
466 | }
467 |
468 | /**
469 | * @since 2.5
470 | */
471 | public MaskPasswordsConfig getConfig() {
472 | return MaskPasswordsConfig.getInstance();
473 | }
474 |
475 | @Override
476 | public String getDisplayName() {
477 | return new Localizable(ResourceBundleHolder.get(MaskPasswordsBuildWrapper.class), "DisplayName").toString();
478 | }
479 |
480 | @Override
481 | public boolean isApplicable(AbstractProject, ?> item) {
482 | return true;
483 | }
484 |
485 | }
486 |
487 | /**
488 | * We need this converter to handle marshalling/unmarshalling of the build
489 | * wrapper data: Relying on the default mechanism doesn't make it (because
490 | * {@link Secret} doesn't have the {@code DataBoundConstructor} annotation).
491 | */
492 | public static final class ConverterImpl implements Converter {
493 |
494 | private final static String VAR_PASSWORD_PAIRS_NODE = "varPasswordPairs";
495 | private final static String VAR_PASSWORD_PAIR_NODE = "varPasswordPair";
496 | private final static String VAR_MASK_REGEXES_NODE = "varMaskRegexes";
497 | private final static String VAR_MASK_REGEX_NODE = "varMaskRegex";
498 | private final static String VAR_ATT = "var";
499 | private final static String PASSWORD_ATT = "password";
500 | private final static String REGEX_ATT = "regex";
501 | private final static String REGEX_NAME = "name";
502 |
503 | public boolean canConvert(Class clazz) {
504 | return clazz.equals(MaskPasswordsBuildWrapper.class);
505 | }
506 |
507 | public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext mc) {
508 | MaskPasswordsBuildWrapper maskPasswordsBuildWrapper = (MaskPasswordsBuildWrapper) o;
509 |
510 | // varPasswordPairs
511 | if(maskPasswordsBuildWrapper.getVarPasswordPairs() != null) {
512 | writer.startNode(VAR_PASSWORD_PAIRS_NODE);
513 | for(VarPasswordPair varPasswordPair: maskPasswordsBuildWrapper.getVarPasswordPairs()) {
514 | // blank passwords are skipped
515 | if(varPasswordPair.getPlainTextPassword() == null) {
516 | continue;
517 | }
518 | writer.startNode(VAR_PASSWORD_PAIR_NODE);
519 | writer.addAttribute(VAR_ATT, varPasswordPair.getVar());
520 | writer.addAttribute(PASSWORD_ATT, varPasswordPair.getPassword().getEncryptedValue());
521 | writer.endNode();
522 | }
523 | writer.endNode();
524 | }
525 | // varMaskRegexes
526 | if(maskPasswordsBuildWrapper.getVarMaskRegexes() != null) {
527 | writer.startNode(VAR_MASK_REGEXES_NODE);
528 | for(VarMaskRegexEntry varMaskRegex: maskPasswordsBuildWrapper.getVarMaskRegexes()) {
529 | // blank passwords are skipped
530 | if(StringUtils.isBlank(varMaskRegex.getRegexString())) {
531 | continue;
532 | }
533 | writer.startNode(VAR_MASK_REGEX_NODE);
534 | writer.addAttribute(REGEX_NAME, varMaskRegex.getKey());
535 | writer.addAttribute(REGEX_ATT, varMaskRegex.getRegexString());
536 | writer.endNode();
537 | }
538 | writer.endNode();
539 | }
540 | }
541 |
542 | public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext uc) {
543 | List varPasswordPairs = new ArrayList<>();
544 | List varMaskRegexes = new ArrayList<>();
545 |
546 | while(reader.hasMoreChildren()) {
547 | reader.moveDown();
548 | if(reader.getNodeName().equals(VAR_PASSWORD_PAIRS_NODE)) {
549 | while(reader.hasMoreChildren()) {
550 | reader.moveDown();
551 | if(reader.getNodeName().equals(VAR_PASSWORD_PAIR_NODE)) {
552 | varPasswordPairs.add(new VarPasswordPair(
553 | reader.getAttribute(VAR_ATT),
554 | Secret.fromString(reader.getAttribute(PASSWORD_ATT))));
555 | }
556 | else {
557 | LOGGER.log(Level.WARNING,
558 | "Encountered incorrect node name: Expected \"" + VAR_PASSWORD_PAIR_NODE + "\", got \"{0}\"",
559 | reader.getNodeName());
560 | }
561 | reader.moveUp();
562 | }
563 | reader.moveUp();
564 | }
565 | else if(reader.getNodeName().equals(VAR_MASK_REGEXES_NODE)) {
566 | while(reader.hasMoreChildren()) {
567 | reader.moveDown();
568 | if(reader.getNodeName().equals(VAR_MASK_REGEX_NODE)) {
569 | varMaskRegexes.add(new VarMaskRegexEntry(
570 | reader.getAttribute(REGEX_NAME),
571 | reader.getAttribute(REGEX_ATT)));
572 | }
573 | else {
574 | LOGGER.log(Level.WARNING,
575 | "Encountered incorrect node name: Expected \"" + VAR_MASK_REGEX_NODE + "\", got \"{0}\"",
576 | reader.getNodeName());
577 | }
578 | reader.moveUp();
579 | }
580 | reader.moveUp();
581 | }
582 | else {
583 | LOGGER.log(Level.WARNING,
584 | "Encountered incorrect node name: \"{0}\"", reader.getNodeName());
585 | }
586 | }
587 |
588 | return new MaskPasswordsBuildWrapper(varPasswordPairs, varMaskRegexes);
589 | }
590 |
591 | }
592 |
593 | private static final Logger LOGGER = Logger.getLogger(MaskPasswordsBuildWrapper.class.getName());
594 |
595 | }
596 |
--------------------------------------------------------------------------------
/src/main/java/com/michelin/cio/hudson/plugins/maskpasswords/MaskPasswordsConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2011, Manufacture Francaise des Pneumatiques Michelin, Romain Seguy
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package com.michelin.cio.hudson.plugins.maskpasswords;
26 |
27 | import com.google.common.annotations.VisibleForTesting;
28 | import com.michelin.cio.hudson.plugins.maskpasswords.MaskPasswordsBuildWrapper.VarMaskRegex;
29 | import com.michelin.cio.hudson.plugins.maskpasswords.MaskPasswordsBuildWrapper.VarPasswordPair;
30 | import edu.umd.cs.findbugs.annotations.CheckForNull;
31 | import edu.umd.cs.findbugs.annotations.NonNull;
32 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
33 | import hudson.Extension;
34 | import hudson.ExtensionList;
35 | import hudson.XmlFile;
36 | import hudson.cli.CLICommand;
37 | import hudson.model.AbstractDescribableImpl;
38 | import hudson.model.Descriptor;
39 | import hudson.model.ParameterDefinition;
40 | import hudson.model.ParameterDefinition.ParameterDescriptor;
41 | import hudson.model.ParameterValue;
42 | import jenkins.model.Jenkins;
43 | import net.sf.json.JSONObject;
44 | import org.apache.commons.lang.StringUtils;
45 | import org.jenkinsci.plugins.structs.describable.CustomDescribableModel;
46 | import org.jenkinsci.plugins.structs.describable.UninstantiatedDescribable;
47 | import org.kohsuke.accmod.Restricted;
48 | import org.kohsuke.accmod.restrictions.NoExternalUse;
49 | import org.kohsuke.stapler.DataBoundConstructor;
50 | import org.kohsuke.stapler.StaplerRequest2;
51 |
52 | import net.jcip.annotations.GuardedBy;
53 | import java.io.File;
54 | import java.io.FileNotFoundException;
55 | import java.io.IOException;
56 | import java.lang.reflect.Method;
57 | import java.nio.file.NoSuchFileException;
58 | import java.util.ArrayList;
59 | import java.util.HashMap;
60 | import java.util.HashSet;
61 | import java.util.LinkedHashSet;
62 | import java.util.List;
63 | import java.util.Map;
64 | import java.util.Set;
65 | import java.util.logging.Level;
66 | import java.util.logging.Logger;
67 |
68 | /**
69 | * Singleton class to manage Mask Passwords global settings.
70 | *
71 | * @author Romain Seguy (http://openromain.blogspot.com)
72 | * @since 2.5
73 | */
74 | public class MaskPasswordsConfig {
75 |
76 | private final static String CONFIG_FILE = "com.michelin.cio.hudson.plugins.maskpasswords.MaskPasswordsConfig.xml";
77 | private static Object CONFIG_FILE_LOCK = new Object();
78 | @GuardedBy("CONFIG_FILE_LOCK")
79 | private static MaskPasswordsConfig config;
80 |
81 | /**
82 | * Contains the set of {@link ParameterDefinition}s whose value must be
83 | * masked in builds' console.
84 | */
85 | @GuardedBy("this")
86 | private Set maskPasswordsParamDefClasses;
87 | /**
88 | * Contains the set of {@link ParameterValue}s whose value must be masked in
89 | * builds' console.
90 | */
91 | @NonNull
92 | @GuardedBy("this")
93 | private transient Set paramValueCache_maskedClasses = new HashSet<>();
94 |
95 | /**
96 | * Cache of values, which are not subjects for masking.
97 | */
98 | @NonNull
99 | @GuardedBy("this")
100 | private transient Set paramValueCache_nonMaskedClasses = new HashSet<>();
101 |
102 | /**
103 | * Users can define key/password pairs at the global level to share common
104 | * passwords with several jobs.
105 | *
106 | *
Never ever use this attribute directly: Use {@link #getGlobalVarPasswordPairsList} to avoid
107 | * potential NPEs.
108 | *
109 | * @since 2.7
110 | */
111 | private List globalVarPasswordPairs;
112 | /**
113 | * Users can define regexes at the global level to mask in jobs.
114 | *
115 | *
Never ever use this attribute directly: Use {@link #getGlobalVarMaskRegexesMap} to avoid
116 | * potential NPEs.
117 | *
118 | * @since 2.9
119 | *
120 | * Deprecated in favor of globalVarMaskRegexesMap which has label names mapped to value's for better identification
121 | */
122 | @Deprecated
123 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Retain API compatibility")
124 | public List globalVarMaskRegexes;
125 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Retain API compatibility")
126 | public List globalVarMaskRegexesU;
127 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Retain API compatibility")
128 | public HashMap globalVarMaskRegexesMap;
129 | /**
130 | * Whether or not to enable the plugin globally on ALL BUILDS.
131 | *
132 | * @since 2.9
133 | */
134 | private boolean globalVarEnableGlobally;
135 |
136 | public MaskPasswordsConfig() {
137 | maskPasswordsParamDefClasses = new LinkedHashSet<>();
138 | reset();
139 | }
140 |
141 | @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "readResolve()")
142 | private Object readResolve() {
143 | // Reinit caches
144 | synchronized(this) {
145 | if (paramValueCache_maskedClasses == null) {
146 | paramValueCache_maskedClasses = new HashSet<>();
147 | }
148 | if (paramValueCache_nonMaskedClasses == null) {
149 | paramValueCache_nonMaskedClasses = new HashSet<>();
150 | }
151 | }
152 |
153 | return this;
154 | }
155 |
156 | private boolean isGlobalVarMaskRegexesNull() {
157 | return (this.globalVarMaskRegexesMap == null) || (this.globalVarMaskRegexesU == null);
158 | }
159 |
160 | /**
161 | * Adds a key/password pair at the global level.
162 | *
163 | *
If either key or password is blank (as defined per the Commons Lang
164 | * library), then the pair is not added.
165 | *
166 | * @since 2.7
167 | */
168 | public void addGlobalVarPasswordPair(VarPasswordPair varPasswordPair) {
169 | // blank values are forbidden
170 | if(StringUtils.isBlank(varPasswordPair.getVar()) || varPasswordPair.getPlainTextPassword() == null) {
171 | LOGGER.fine("addGlobalVarPasswordPair NOT adding pair with null var or password");
172 | return;
173 | }
174 | getGlobalVarPasswordPairsList().add(varPasswordPair);
175 | }
176 |
177 | /**
178 | * Adds a regex at the global level.
179 | *
180 | *
If regex is blank (as defined per the Commons Lang
181 | * library), then the pair is not added.
182 | *
183 | * @since 2.9
184 | */
185 | public void addGlobalVarMaskRegex(VarMaskRegex varMaskRegex) {
186 | addGlobalVarMaskRegex("", varMaskRegex);
187 | }
188 |
189 | public void addGlobalVarMaskRegex(String name, String regex) {
190 | addGlobalVarMaskRegex(name, new VarMaskRegex(regex));
191 | }
192 |
193 | public void addGlobalVarMaskRegex(String name, VarMaskRegex varMaskRegex) {
194 | // blank values are forbidden
195 | if(StringUtils.isBlank(varMaskRegex.getRegex())) {
196 | LOGGER.fine("addGlobalVarMaskRegex NOT adding null regex");
197 | return;
198 | }
199 | // blank values are forbidden, will give default numbered key name
200 | if(StringUtils.isBlank(name)) {
201 | LOGGER.fine("Generating default numbered key for VarMaskRegex");
202 | name = "VarMaskRegex" + getGlobalVarMaskRegexesMap().size();
203 | }
204 | HashMap regexMap = getGlobalVarMaskRegexesMap();
205 | regexMap.put(name, varMaskRegex);
206 | getGlobalVarMaskRegexesUList().clear();
207 | for (Map.Entry entry: getGlobalVarMaskRegexesMap().entrySet()) {
208 | getGlobalVarMaskRegexesUList().add(new VarMaskRegexEntry(entry.getKey(), entry.getValue()));
209 | }
210 |
211 | saveSafeIO(this);
212 | }
213 |
214 | public void removeGlobalVarMaskRegexByName(@NonNull String name) {
215 | if (!isGlobalVarMaskRegexesNull()) {
216 | VarMaskRegex r = getGlobalVarMaskRegexesMap().get(name);
217 | if (r != null) {
218 | VarMaskRegexEntry e = new VarMaskRegexEntry(name, r);
219 | getGlobalVarMaskRegexesMap().remove(name);
220 | getGlobalVarMaskRegexesUList().remove(e);
221 | }
222 | }
223 | saveSafeIO(this);
224 | }
225 |
226 | public void removeGlobalVarMaskRegex(String name, String regex) {
227 | if (!isGlobalVarMaskRegexesNull()) {
228 | VarMaskRegexEntry e = new VarMaskRegexEntry(name, regex);
229 | if (getGlobalVarMaskRegexesUList().remove(e)) {
230 | HashMap map = getGlobalVarMaskRegexesMap();
231 | VarMaskRegex r = map.get(name);
232 | if (r != null && r.getRegex() != null && regex != null && r.getRegex().equals(regex)) {
233 | map.remove(name);
234 | }
235 | }
236 |
237 | }
238 | saveSafeIO(this);
239 | }
240 |
241 | /**
242 | * @param className The class key of a {@link ParameterDescriptor} to be added
243 | * to the list of parameters which will prevent the rebuild
244 | * action to be enabled for a build
245 | */
246 | public synchronized void addMaskedPasswordParameterDefinition(String className) {
247 | maskPasswordsParamDefClasses.add(className);
248 | // Maybe is it masked now
249 | paramValueCache_nonMaskedClasses.clear();
250 | }
251 |
252 | public void setGlobalVarEnabledGlobally(boolean state) {
253 | globalVarEnableGlobally = state;
254 | }
255 |
256 | /**
257 | * Resets configuration to the default state.
258 | */
259 | @Restricted(NoExternalUse.class)
260 | @VisibleForTesting
261 | public final synchronized void reset() {
262 | // Wipe the data
263 | clear();
264 |
265 | // default values for the first time the config is created
266 | addMaskedPasswordParameterDefinition(hudson.model.PasswordParameterDefinition.class.getName());
267 | addMaskedPasswordParameterDefinition(com.michelin.cio.hudson.plugins.passwordparam.PasswordParameterDefinition.class.getName());
268 | }
269 |
270 | public synchronized void clear() {
271 | maskPasswordsParamDefClasses.clear();
272 | getGlobalVarPasswordPairsList().clear();
273 | getGlobalVarMaskRegexesList().clear();
274 | getGlobalVarMaskRegexesUList().clear();
275 | getGlobalVarMaskRegexesMap().clear();
276 | globalVarEnableGlobally = false;
277 |
278 | // Drop caches
279 | invalidatePasswordValueClassCaches();
280 | }
281 |
282 | public synchronized void clear(boolean doSave) {
283 | clear();
284 | if (doSave) {
285 | saveSafeIO(this);
286 | }
287 | }
288 |
289 | /*package*/ synchronized void invalidatePasswordValueClassCaches() {
290 | paramValueCache_maskedClasses.clear();
291 | paramValueCache_nonMaskedClasses.clear();
292 | }
293 |
294 | public static MaskPasswordsConfig getInstance() {
295 | synchronized(CONFIG_FILE_LOCK) {
296 | if(config == null) {
297 | config = load();
298 | }
299 | return config;
300 | }
301 | }
302 |
303 | private static XmlFile getConfigFile() {
304 | return new XmlFile(new File(Jenkins.get().getRootDir(), CONFIG_FILE));
305 | }
306 |
307 | /**
308 | * Returns the list of key/password pairs defined at the global level.
309 | *
310 | *
Modifications broughts to the returned list has no impact on this
311 | * configuration (the returned value is a copy). Also, the list can be
312 | * empty but never {@code null}.
313 | *
314 | * @since 2.7
315 | */
316 | public List getGlobalVarPasswordPairs() {
317 | List r = new ArrayList<>(getGlobalVarPasswordPairsList().size());
318 |
319 | // deep copy
320 | for(VarPasswordPair varPasswordPair: getGlobalVarPasswordPairsList()) {
321 | r.add((VarPasswordPair) varPasswordPair.clone());
322 | }
323 | return r;
324 | }
325 |
326 | /**
327 | * Returns the list of regexes defined at the global level.
328 | *
329 | *
Modifications broughts to the returned list has no impact on this
330 | * configuration (the returned value is a copy). Also, the list can be
331 | * empty but never {@code null}.
332 | *
333 | * @since 2.9
334 | */
335 | public List getGlobalVarMaskRegexesU() {
336 | List r = new ArrayList<>(getGlobalVarMaskRegexesMap().size());
337 |
338 | // deep copy
339 | for(Map.Entry entry: getGlobalVarMaskRegexesMap().entrySet()) {
340 | r.add(new VarMaskRegexEntry(entry.getKey(), (VarMaskRegex) entry.getValue().clone()));
341 | }
342 |
343 | return r;
344 | }
345 |
346 |
347 | /**
348 | * Fixes JENKINS-11514: When {@code MaskPasswordsConfig.xml} is there but was created from
349 | * version 2.6.1 (or older) of the plugin, {@link #globalVarPasswordPairs} can actually be
350 | * {@code null} ==> Always use this getter to avoid NPEs.
351 | *
352 | * @since 2.7.1
353 | */
354 | private List getGlobalVarPasswordPairsList() {
355 | if(globalVarPasswordPairs == null) {
356 | globalVarPasswordPairs = new ArrayList<>();
357 | }
358 | return globalVarPasswordPairs;
359 | }
360 |
361 | /**
362 | * Fixes JENKINS-11514: When {@code MaskPasswordsConfig.xml} is there but was created from
363 | * version 2.8 (or older) of the plugin, {@link #globalVarPasswordPairs} can actually be
364 | * {@code null} ==> Always use this getter to avoid NPEs.
365 | *
366 | * @since 2.9
367 | */
368 | @Deprecated
369 | public List getGlobalVarMaskRegexesList() {
370 | if(globalVarMaskRegexes == null) {
371 | globalVarMaskRegexes = new ArrayList<>();
372 | }
373 | return globalVarMaskRegexes;
374 | }
375 |
376 | public List getGlobalVarMaskRegexesUList() {
377 | if (this.globalVarMaskRegexesU == null) {
378 | globalVarMaskRegexesU = new ArrayList<>();
379 | }
380 | return globalVarMaskRegexesU;
381 | }
382 |
383 | public HashMap getGlobalVarMaskRegexesMap() {
384 | if (globalVarMaskRegexesMap == null) {
385 | globalVarMaskRegexesMap = new HashMap<>();
386 | /* upon initialization, create entries from globalVarMaskRegex (List) */
387 | LOGGER.info("Initializing global var mask regexes map");
388 | if (getGlobalVarMaskRegexesList().size() > 0) {
389 | for (int i = 0 ; i < getGlobalVarMaskRegexesList().size(); i++) {
390 | globalVarMaskRegexesMap.put("Regex_" + String.valueOf(i), getGlobalVarMaskRegexesList().get(i));
391 | }
392 | getGlobalVarMaskRegexesList().clear();
393 | try {
394 | save(this);
395 | } catch (IOException e) {
396 | LOGGER.info("IO Exception when trying to initialize global var mask value map from list:\n" + e.getMessage());
397 | }
398 | }
399 | }
400 | return globalVarMaskRegexesMap;
401 | }
402 |
403 |
404 | /**
405 | * Returns a map of all {@link ParameterDefinition}s that can be used in
406 | * jobs.
407 | *
408 | *
The key is the class key of the {@link ParameterDefinition}, the value
409 | * is its display key.
410 | */
411 | public static Map getParameterDefinitions() {
412 | Map params = new HashMap<>();
413 |
414 | ExtensionList paramExtensions =
415 | Jenkins.get().getExtensionList(ParameterDefinition.ParameterDescriptor.class);
416 | for(ParameterDefinition.ParameterDescriptor paramExtension: paramExtensions) {
417 | // we need the getEnclosingClass() to drop the inner ParameterDescriptor
418 | // and work directly with the ParameterDefinition
419 | params.put(paramExtension.getClass().getEnclosingClass().getName(), paramExtension.getDisplayName());
420 | }
421 |
422 | return params;
423 | }
424 |
425 | /**
426 | * Returns whether the plugin is enabled globally for ALL BUILDS.
427 | */
428 | public boolean isEnabledGlobally() {
429 | return globalVarEnableGlobally;
430 | }
431 |
432 | /**
433 | * Check if the parameter value class needs to be masked
434 | * @deprecated There is a high risk of false-negatives. Use {@link #isMasked(hudson.model.ParameterValue, java.lang.String)} at least
435 | * @param paramValueClassName Class key of the {@link ParameterValue}
436 | * @return {@code true} if the parameter value should be masked.
437 | * {@code false} if the plugin is not sure, may be false-negative
438 | */
439 | @Deprecated
440 | public synchronized boolean isMasked(final @NonNull String paramValueClassName) {
441 | return isMasked(null, paramValueClassName);
442 | }
443 |
444 | /**
445 | * Returns true if the specified parameter value class key corresponds to
446 | * a parameter definition class key selected in Jenkins' main
447 | * configuration screen.
448 | * @param value Parameter value. Without it there is a high risk of false negatives.
449 | * @param paramValueClassName Class key of the {@link ParameterValue} class implementation
450 | * @return {@code true} if the parameter value should be masked.
451 | * {@code false} if the plugin is not sure, may be false-negative especially if the value is {@code null}.
452 | * @since 2.10
453 | */
454 | public boolean isMasked(final @CheckForNull ParameterValue value,
455 | final @NonNull String paramValueClassName) {
456 |
457 | // We always mask sensitive variables, the configuration does not matter in such case
458 | if (value != null && value.isSensitive()) {
459 | return true;
460 | }
461 |
462 | synchronized(this) {
463 | // Check if the value is in the cache
464 | if (paramValueCache_maskedClasses.contains(paramValueClassName)) {
465 | return true;
466 | }
467 | if (paramValueCache_nonMaskedClasses.contains(paramValueClassName)) {
468 | return false;
469 | }
470 |
471 | // Now guess
472 | boolean guessSo = guessIfShouldMask(paramValueClassName);
473 | if (guessSo) {
474 | // We are pretty sure it requires masking
475 | paramValueCache_maskedClasses.add(paramValueClassName);
476 | return true;
477 | } else {
478 | // It does not require masking, but we are not so sure
479 | // The warning will be printed each time the cache is invalidated due to whatever reason
480 | LOGGER.log(Level.WARNING, "Identified the {0} class as a ParameterValue class, which does not require masking. It may be false-negative", paramValueClassName);
481 | paramValueCache_nonMaskedClasses.add(paramValueClassName);
482 | return false;
483 | }
484 | }
485 | }
486 |
487 | //TODO: add support of specifying masked parameter values byt the... parameter value classs key. So obvious, yeah?
488 | /**
489 | * Tries to guess if the parameter value class should be masked.
490 | * @param paramValueClassName Parameter value class key
491 | * @return {@code true} if we are sure that the class has to be masked
492 | * {@code false} otherwise, there is a risk of false negative due to the presumptions.
493 | */
494 | /*package*/ synchronized boolean guessIfShouldMask(final @NonNull String paramValueClassName) {
495 | // The only way to find parameter definition/parameter value
496 | // couples is to reflect the methods of parameter definition
497 | // classes which instantiate the parameter value.
498 | // This means that this algorithm expects that the developers do
499 | // clearly redefine the return type when implementing parameter
500 | // definitions/values.
501 | for(String paramDefClassName: maskPasswordsParamDefClasses) {
502 | final Class> paramDefClass;
503 | try {
504 | paramDefClass = Jenkins.get().getPluginManager().uberClassLoader.loadClass(paramDefClassName);
505 | } catch (ClassNotFoundException ex) {
506 | LOGGER.log(Level.WARNING, "Cannot check ParamDef for masking " + paramDefClassName, ex);
507 | continue;
508 | }
509 |
510 | tryProcessMethod(paramDefClass, "getDefaultParameterValue", true);
511 | tryProcessMethod(paramDefClass, "createValue", true, StaplerRequest2.class, JSONObject.class);
512 | tryProcessMethod(paramDefClass, "createValue", true, StaplerRequest2.class);
513 | tryProcessMethod(paramDefClass, "createValue", true, CLICommand.class, String.class);
514 | // This custom implementation is not a part of the API, but let's try it
515 | tryProcessMethod(paramDefClass, "createValue", false, String.class);
516 |
517 | // If the parameter value class has been added to the cache, exit
518 | if (paramValueCache_maskedClasses.contains(paramValueClassName)) {
519 | return true;
520 | }
521 | }
522 |
523 | // Always mask the hudson.model.PasswordParameterValue class and its overrides
524 | // This class does not comply with the criteria above, but it is sensitive starting from 1.378
525 | final Class> valueClass;
526 | try {
527 | valueClass = Jenkins.get().getPluginManager().uberClassLoader.loadClass(paramValueClassName);
528 | } catch (Exception ex) {
529 | // Move on. Whatever happens here, it will blow up somewhere else
530 | LOGGER.log(Level.FINE, "Failed to load class for the ParameterValue " + paramValueClassName, ex);
531 | return false;
532 | }
533 |
534 | return hudson.model.PasswordParameterValue.class.isAssignableFrom(valueClass);
535 | }
536 |
537 | /**
538 | * Processes the methods in the {@link ParameterValue} class and caches all ParameterValue implementations as ones requiring masking.
539 | * @param clazz Class
540 | * @param methodName Method key
541 | * @param parameterTypes Parameters
542 | */
543 | private synchronized void tryProcessMethod(Class> clazz, String methodName, boolean expectedToExist, Class> ... parameterTypes) {
544 |
545 | final Method method;
546 | try {
547 | method = clazz.getMethod(methodName, parameterTypes);
548 | } catch (NoSuchMethodException ex) {
549 | Level logLevel = expectedToExist ? Level.INFO : Level.CONFIG;
550 | if (LOGGER.isLoggable(logLevel)) {
551 | String methodSpec = String.format("%s(%s)", methodName, StringUtils.join(parameterTypes, ","));
552 | LOGGER.log(logLevel, "No method {0} for class {1}", new Object[] {methodSpec, clazz});
553 | }
554 | return;
555 | } catch (RuntimeException ex) {
556 | Level logLevel = expectedToExist ? Level.INFO : Level.CONFIG;
557 | if (LOGGER.isLoggable(logLevel)) {
558 | String methodSpec = String.format("%s(%s)", methodName, StringUtils.join(parameterTypes, ","));
559 | LOGGER.log(logLevel, "Failed to retrieve the method {0} for class {1}", new Object[] {methodSpec, clazz});
560 | }
561 | return;
562 | }
563 |
564 | Class> returnType = method.getReturnType();
565 | // We do not veto the the root class
566 | if (ParameterValue.class.isAssignableFrom(returnType)) {
567 | if (!ParameterValue.class.equals(returnType)) {
568 | // Add this class to the cache
569 | paramValueCache_maskedClasses.add(returnType.getName());
570 | }
571 | }
572 | }
573 |
574 | /**
575 | * Returns true if the specified parameter definition class key has been
576 | * selected in Jenkins main configuration screen.
577 | */
578 | public synchronized boolean isSelected(String paramDefClassName) {
579 | return maskPasswordsParamDefClasses.contains(paramDefClassName);
580 | }
581 |
582 | public static MaskPasswordsConfig load() {
583 | LOGGER.entering(CLASS_NAME, "load");
584 | try {
585 | MaskPasswordsConfig file = (MaskPasswordsConfig) getConfigFile().read();
586 | return (MaskPasswordsConfig) getConfigFile().read();
587 | }
588 | catch(FileNotFoundException | NoSuchFileException e) {
589 | LOGGER.log(Level.WARNING, "No configuration found for Mask Passwords plugin");
590 | }
591 | catch(Exception e) {
592 | LOGGER.log(Level.WARNING, "Unable to load Mask Passwords plugin configuration from " + CONFIG_FILE, e);
593 | }
594 | LOGGER.log(Level.FINE, "No Mask Passwords config file loaded; using defaults");
595 | return new MaskPasswordsConfig();
596 | }
597 |
598 | public static void save(MaskPasswordsConfig config) throws IOException {
599 | LOGGER.entering(CLASS_NAME, "save");
600 | getConfigFile().write(config);
601 | LOGGER.exiting(CLASS_NAME, "save");
602 | }
603 |
604 | static void saveSafeIO(MaskPasswordsConfig config) {
605 | try {
606 | save(config);
607 | } catch(IOException e) {
608 | LOGGER.warning("Failed to save MaskPasswordsConfig due to IOException: " + e.getMessage());
609 | }
610 | }
611 |
612 | public String toString() {
613 | StringBuilder sb = new StringBuilder("MaskPasswordsConfigFile Regexes:[\n");
614 | for (Map.Entry entry : this.getGlobalVarMaskRegexesMap().entrySet()) {
615 | sb.append(entry.getKey() + " : " + entry.getValue().getRegex() + "\n");
616 | }
617 | sb.append("]");
618 | return sb.toString();
619 | }
620 |
621 | private final static String CLASS_NAME = MaskPasswordsConfig.class.getName();
622 | private final static Logger LOGGER = Logger.getLogger(CLASS_NAME);
623 |
624 | public static class VarMaskRegexEntry extends AbstractDescribableImpl implements Cloneable{
625 | private String key;
626 | private VarMaskRegex value;
627 |
628 | @DataBoundConstructor
629 | public VarMaskRegexEntry(String key, String value) {
630 | this.key = key;
631 | this.value = new VarMaskRegex(value);
632 | }
633 |
634 | public VarMaskRegexEntry(String key, VarMaskRegex value) {
635 | this.key = key;
636 | this.value = value;
637 | }
638 |
639 | public VarMaskRegexEntry(VarMaskRegex value) {
640 | key = "";
641 | this.value = value;
642 | }
643 |
644 | public String getName() {
645 | return key;
646 | }
647 |
648 | public void setName(String name) {
649 | this.key = name;
650 | }
651 |
652 | public VarMaskRegex getRegex() {
653 | return value;
654 | }
655 |
656 | public void setRegex(VarMaskRegex regex) {
657 | this.value = regex;
658 | }
659 |
660 | public String getKey() {
661 | return this.key;
662 | }
663 |
664 | public void setKey(String key) {
665 | this.key = key;
666 | }
667 |
668 | public String getValue() {
669 | return this.getRegex().getRegex();
670 | }
671 | public void setValue(String regex) {
672 | this.setRegex(new VarMaskRegex(regex));
673 | }
674 |
675 | public String getRegexString() {
676 | if (this.value == null) {
677 | return "";
678 | }
679 | return this.value.getRegex();
680 | }
681 |
682 | public String toString() {
683 | return this.key + ":" + this.value;
684 | }
685 |
686 | @Override
687 | @SuppressFBWarnings(value = "CN_IDIOM_NO_SUPER_CALL", justification = "We do not expect anybody to use this class."
688 | + "If they do, they must override clone() as well")
689 | public Object clone() {
690 | return new VarMaskRegexEntry(this.getKey(), this.getRegex());
691 | }
692 |
693 | @Override
694 | public int hashCode() {
695 | int hash = 3;
696 | hash = 67 * hash + (this.key != null ? this.key.hashCode() : 0);
697 | return hash;
698 | }
699 |
700 | @Override
701 | public boolean equals(Object other) {
702 | if (other == null) {
703 | return false;
704 | } else if (!this.getClass().equals(other.getClass())) {
705 | return false;
706 | } else {
707 | VarMaskRegexEntry otherE = (VarMaskRegexEntry) other;
708 | return (this.getKey().equals(otherE.getKey())) && this.getRegex().equals(otherE.getRegex());
709 | }
710 | }
711 |
712 | @Extension
713 | public static class DescriptorImpl extends Descriptor implements CustomDescribableModel {
714 | public String getDisplayName() {
715 | return VarMaskRegexEntry.class.getName();
716 | }
717 |
718 | @NonNull
719 | @Override
720 | public UninstantiatedDescribable customUninstantiate(@NonNull UninstantiatedDescribable step) {
721 | Map arguments = step.getArguments();
722 | Map newMap1 = new HashMap<>();
723 | newMap1.put("key", arguments.get("key"));
724 | newMap1.put("value", arguments.get("value"));
725 | return step.withArguments(newMap1);
726 | }
727 |
728 | @NonNull
729 | @Override
730 | public Map customInstantiate(@NonNull Map arguments) {
731 | Map newMap = new HashMap<>();
732 | newMap.put("key", arguments.get("key"));
733 | newMap.put("value", String.valueOf(arguments.get("value")));
734 | return newMap;
735 | }
736 | }
737 | }
738 |
739 | }
740 |
--------------------------------------------------------------------------------
/src/main/java/com/michelin/cio/hudson/plugins/maskpasswords/MaskPasswordsConsoleLogFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2016 Cox Automotive, Inc./Manheim, Jason Antman.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package com.michelin.cio.hudson.plugins.maskpasswords;
26 |
27 | import hudson.Extension;
28 | import hudson.console.ConsoleLogFilter;
29 | import hudson.model.Run;
30 |
31 | import java.io.IOException;
32 | import java.io.OutputStream;
33 | import java.io.Serializable;
34 | import java.util.ArrayList;
35 | import java.util.List;
36 | import java.util.logging.Level;
37 | import java.util.logging.Logger;
38 |
39 | /**
40 | * GLOBAL Console Log Filter that alters the console so that passwords don't
41 | * get displayed.
42 | *
43 | * @author Jason Antman jason@jasonantman.com
44 | */
45 | @Extension
46 | public class MaskPasswordsConsoleLogFilter extends ConsoleLogFilter implements Serializable {
47 |
48 | private static final long serialVersionUID = 1L;
49 |
50 | public MaskPasswordsConsoleLogFilter() {
51 | // nothing to do here; this object lives for the lifetime of Jenkins,
52 | // so if we don't want to have to restart to detect config changes,
53 | // we need to get the config in each run.
54 | }
55 |
56 | @SuppressWarnings("rawtypes")
57 | @Override
58 | public OutputStream decorateLogger(Run _ignore, OutputStream logger) throws IOException, InterruptedException {
59 | // check the config
60 | MaskPasswordsConfig config = MaskPasswordsConfig.getInstance();
61 | if(! config.isEnabledGlobally()) {
62 | LOGGER.log(Level.FINE, "MaskPasswords not enabled globally; not decorating logger");
63 | return logger;
64 | }
65 | LOGGER.log(Level.FINE, "MaskPasswords IS enabled globally; decorating logger");
66 |
67 | // build our config
68 | List passwords = new ArrayList<>();
69 | List regexes = new ArrayList<>();
70 |
71 | // global passwords
72 | List globalVarPasswordPairs = config.getGlobalVarPasswordPairs();
73 | for(MaskPasswordsBuildWrapper.VarPasswordPair globalVarPasswordPair: globalVarPasswordPairs) {
74 | passwords.add(globalVarPasswordPair.getPlainTextPassword());
75 | }
76 |
77 | // global regexes
78 | List globalVarMaskRegexes = config.getGlobalVarMaskRegexesU();
79 | for(MaskPasswordsConfig.VarMaskRegexEntry globalVarMaskRegex: globalVarMaskRegexes) {
80 | regexes.add(globalVarMaskRegex.getValue());
81 | }
82 | return new MaskPasswordsOutputStream(logger, passwords, regexes);
83 | }
84 |
85 | private static final Logger LOGGER = Logger.getLogger(MaskPasswordsConsoleLogFilter.class.getName());
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/com/michelin/cio/hudson/plugins/maskpasswords/MaskPasswordsOutputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2010-2011, Manufacture Francaise des Pneumatiques Michelin,
5 | * Romain Seguy
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | package com.michelin.cio.hudson.plugins.maskpasswords;
27 |
28 | import edu.umd.cs.findbugs.annotations.CheckForNull;
29 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
30 | import hudson.console.LineTransformationOutputStream;
31 | import org.apache.commons.lang.StringUtils;
32 |
33 | import java.io.IOException;
34 | import java.io.OutputStream;
35 | import java.io.UnsupportedEncodingException;
36 | import java.net.URLEncoder;
37 | import java.util.ArrayList;
38 | import java.util.Collection;
39 | import java.util.List;
40 | import java.util.regex.Pattern;
41 |
42 | import static com.michelin.cio.hudson.plugins.util.MaskPasswordsUtil.secretsMaskPatterns;
43 |
44 | //TODO: UTF-8 hardcoding is not a perfect solution
45 | /**
46 | * Custom output stream which masks a predefined set of passwords.
47 | *
48 | * @author Romain Seguy (http://openromain.blogspot.com)
49 | */
50 | public class MaskPasswordsOutputStream extends LineTransformationOutputStream {
51 |
52 | private final OutputStream logger;
53 | private final List passwordsAsPatterns;
54 | private final String runName;
55 |
56 | /**
57 | * @param logger The output stream to which this {@link MaskPasswordsOutputStream}
58 | * will write to
59 | * @param passwords A collection of {@link String}s to be masked
60 | * @param regexes A collection of Regular Expression {@link String}s to be masked
61 | * @param runName A string representation of the Run/Build the output stream logger is associated with. Used for logging purposes.
62 | */
63 | public MaskPasswordsOutputStream(OutputStream logger, @CheckForNull Collection passwords, @CheckForNull Collection regexes, String runName) {
64 | this.logger = logger;
65 | this.runName = (runName != null) ? runName : "";
66 | passwordsAsPatterns = new ArrayList<>();
67 |
68 | if (passwords != null) {
69 | // Passwords aggregated into single regex which is compiled as a pattern for efficiency
70 | StringBuilder pwRegex = new StringBuilder().append('(');
71 | int pwCount = 0;
72 | for (String pw : passwords) {
73 | if (StringUtils.isNotEmpty(pw)) {
74 | pwCount++;
75 | pwRegex.append(Pattern.quote(pw));
76 | pwRegex.append('|');
77 | try {
78 | String encodedPassword = URLEncoder.encode(pw, "UTF-8");
79 | if (!encodedPassword.equals(pw)) {
80 | pwRegex.append(Pattern.quote(encodedPassword));
81 | pwRegex.append('|');
82 | }
83 | } catch (UnsupportedEncodingException e) {
84 | // ignore any encoding problem => status quo
85 | }
86 | }
87 | }
88 | if (pwCount > 0) {
89 | pwRegex.deleteCharAt(pwRegex.length()-1); // removes the last unuseful pipe
90 | pwRegex.append(')');
91 | passwordsAsPatterns.add(Pattern.compile(pwRegex.toString()));
92 | }
93 | }
94 | if (regexes != null) {
95 | for (String r: regexes) {
96 | passwordsAsPatterns.add(Pattern.compile(r));
97 | }
98 | }
99 |
100 | }
101 |
102 | /**
103 | * @param logger The output stream to which this {@link MaskPasswordsOutputStream}
104 | * will write to
105 | * @param passwords A collection of {@link String}s to be masked
106 | */
107 | public MaskPasswordsOutputStream(OutputStream logger, @CheckForNull Collection passwords) {
108 | this(logger, passwords, null);
109 | }
110 |
111 | public MaskPasswordsOutputStream(OutputStream logger, @CheckForNull Collection passwords, @CheckForNull Collection regexes) {
112 | this(logger, passwords, regexes, "");
113 | }
114 |
115 | // TODO: The logic relies on the default encoding, which may cause issues when master and agent have different encodings
116 | @SuppressFBWarnings(value = "DM_DEFAULT_ENCODING", justification = "Open TODO item for wider rework")
117 | @Override
118 | protected void eol(byte[] bytes, int len) throws IOException {
119 | String line = new String(bytes, 0, len);
120 | if(passwordsAsPatterns != null && line != null) {
121 | line = secretsMaskPatterns(passwordsAsPatterns, line, runName);
122 | }
123 | logger.write(line.getBytes());
124 | }
125 |
126 | /**
127 | * {@inheritDoc}
128 | * @throws IOException on error
129 | */
130 | @Override
131 | public void close() throws IOException {
132 | super.close();
133 | logger.close();
134 | }
135 |
136 | /**
137 | * {@inheritDoc}
138 | * @throws IOException on error
139 | */
140 | @Override
141 | public void flush() throws IOException {
142 | super.flush();
143 | logger.flush();
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/main/java/com/michelin/cio/hudson/plugins/passwordparam/PasswordParameterDefinition.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2004-2009, Sun Microsystems, Inc.
5 | * Copyright (c) 2011, Manufacture Française des Pneumatiques Michelin, Romain Seguy
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 | package com.michelin.cio.hudson.plugins.passwordparam;
26 |
27 | import hudson.model.ParameterValue;
28 | import net.sf.json.JSONObject;
29 | import org.kohsuke.stapler.StaplerRequest2;
30 | import org.kohsuke.stapler.DataBoundConstructor;
31 | import hudson.Extension;
32 | import hudson.model.ParameterDefinition;
33 | import hudson.util.Secret;
34 | import org.jvnet.localizer.ResourceBundleHolder;
35 |
36 | public class PasswordParameterDefinition extends ParameterDefinition {
37 |
38 | @DataBoundConstructor
39 | public PasswordParameterDefinition(String name, String description) {
40 | super(name, description);
41 | }
42 |
43 | public ParameterValue createValue(Secret password) {
44 | PasswordParameterValue value = new PasswordParameterValue(getName(), password, getDescription());
45 | value.setDescription(getDescription());
46 | return value;
47 | }
48 |
49 | @Override
50 | public ParameterValue createValue(StaplerRequest2 req) {
51 | String[] value = req.getParameterValues(getName());
52 | if(value == null) {
53 | return getDefaultParameterValue();
54 | }
55 | else if (value.length != 1) {
56 | throw new IllegalArgumentException("Illegal number of parameter values for " + getName() + ": " + value.length);
57 | }
58 | else {
59 | return createValue(Secret.fromString(value[0]));
60 | }
61 | }
62 |
63 | @Override
64 | public PasswordParameterValue createValue(StaplerRequest2 req, JSONObject formData) {
65 | PasswordParameterValue value = req.bindJSON(PasswordParameterValue.class, formData);
66 | value.setDescription(getDescription());
67 | return value;
68 | }
69 |
70 | @Extension
71 | public final static class ParameterDescriptorImpl extends ParameterDescriptor {
72 |
73 | @Override
74 | public String getDisplayName() {
75 | return ResourceBundleHolder.get(PasswordParameterDefinition.class).format("DisplayName");
76 | }
77 |
78 | @Override
79 | public String getHelpFile() {
80 | return "/help/parameter/string.html";
81 | }
82 |
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/michelin/cio/hudson/plugins/passwordparam/PasswordParameterValue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2004-2009, Sun Microsystems, Inc.
5 | * Copyright (c) 2011-2012, Manufacture Francaise des Pneumatiques Michelin,
6 | * Romain Seguy
7 | *
8 | * Permission is hereby granted, free of charge, to any person obtaining a copy
9 | * of this software and associated documentation files (the "Software"), to deal
10 | * in the Software without restriction, including without limitation the rights
11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | * copies of the Software, and to permit persons to whom the Software is
13 | * furnished to do so, subject to the following conditions:
14 | *
15 | * The above copyright notice and this permission notice shall be included in
16 | * all copies or substantial portions of the Software.
17 | *
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | * THE SOFTWARE.
25 | */
26 | package com.michelin.cio.hudson.plugins.passwordparam;
27 |
28 | import edu.umd.cs.findbugs.annotations.CheckForNull;
29 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
30 | import hudson.EnvVars;
31 | import hudson.model.AbstractBuild;
32 | import hudson.model.ParameterValue;
33 | import hudson.model.Run;
34 | import hudson.util.Secret;
35 | import hudson.util.VariableResolver;
36 | import org.kohsuke.stapler.DataBoundConstructor;
37 |
38 | @SuppressFBWarnings(value = "SE_NO_SERIALVERSIONID", justification = "XStream does not need Serial version ID")
39 | public class PasswordParameterValue extends ParameterValue {
40 |
41 | //TODO: Can it even work with Pipeline? And it should be fine to write secrets
42 | @CheckForNull
43 | @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "The secret must not be stored, so the att has to become transient")
44 | private final transient Secret value;
45 |
46 | public PasswordParameterValue(String name, Secret value, String description) {
47 | super(name, description);
48 | this.value = value;
49 | }
50 |
51 | @DataBoundConstructor
52 | public PasswordParameterValue(String name, String value, String description) {
53 | super(name, description);
54 | this.value = Secret.fromString(value);
55 | }
56 |
57 | @Override
58 | public void buildEnvironment(Run,?> build, EnvVars env) {
59 | env.put(name.toUpperCase(), value != null ? Secret.toString(value) : null);
60 | }
61 |
62 | @Override
63 | public VariableResolver createVariableResolver(AbstractBuild, ?> build) {
64 | return new VariableResolver() {
65 | public String resolve(String name) {
66 | return PasswordParameterValue.this.name.equals(name) ? (value != null ? Secret.toString(value) : null) : null;
67 | }
68 | };
69 | }
70 |
71 | @Override
72 | public final boolean isSensitive() {
73 | return true;
74 | }
75 |
76 | public String getValue() {
77 | return value != null ? Secret.toString(value) : null;
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/com/michelin/cio/hudson/plugins/util/MaskPasswordsUtil.java:
--------------------------------------------------------------------------------
1 | package com.michelin.cio.hudson.plugins.util;
2 |
3 | import edu.umd.cs.findbugs.annotations.CheckForNull;
4 | import org.apache.commons.lang.StringUtils;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Collection;
8 | import java.util.Collections;
9 | import java.util.List;
10 | import java.util.logging.Logger;
11 | import java.util.regex.Matcher;
12 | import java.util.regex.Pattern;
13 |
14 | public class MaskPasswordsUtil {
15 | private static final Logger LOGGER = Logger.getLogger(MaskPasswordsUtil.class.getName());
16 | public final static String MASKED_STRING = "********";
17 |
18 | public static List patternMatch(List ps, String s) {
19 | List ret = new ArrayList<>();
20 | for (Pattern p: ps) {
21 | Matcher m = p.matcher(s);
22 | while (m.find()) { // Regex matches
23 | if (m.groupCount() > 0) { // Regex contains group(s)
24 | for (int i = 1; i <= m.groupCount(); i++) {
25 | String toAdd = m.group(i);
26 | if (toAdd != null) {
27 | ret.add(toAdd);
28 | }
29 | }
30 | } else { // Regex doesn't contain groups, match entire Regex string
31 | ret.add(m.group(0));
32 | }
33 | }
34 | }
35 | return ret;
36 | }
37 |
38 | public static List patternMatch(Pattern p, String s) {
39 | return patternMatch(Collections.singletonList(p), s);
40 | }
41 |
42 | public static String secretsMask(List secrets, String s, String runName) {
43 | if (secrets != null && secrets.size() > 0) {
44 | for (String secret: secrets) {
45 | s = s.replaceAll(Pattern.quote(secret), MASKED_STRING);
46 | }
47 | LOGGER.info(String.format("Masking Run[%s]'s line: %s", runName, StringUtils.strip(s)));
48 | }
49 | return s;
50 | }
51 |
52 | public static String secretsMaskPattern(Pattern p, String s) {
53 | return StringUtils.isNotBlank(s) ? secretsMask(patternMatch(p, s), s, "") : s;
54 | }
55 |
56 | public static String secretsMaskPatterns(List ps, String s, String runName) {
57 | return StringUtils.isNotBlank(s) ? secretsMask(patternMatch(ps, s), s, runName) : s;
58 | }
59 |
60 | public static List passwordRegexCombiner(@CheckForNull Collection passwords, @CheckForNull Collection regexes) {
61 | List passwordsAsPatterns = new ArrayList<>();
62 |
63 | if (passwords != null) {
64 | for (String pw : passwords) {
65 | passwordsAsPatterns.add(Pattern.compile(pw));
66 | }
67 | }
68 | if (regexes != null) {
69 | for (String r: regexes) {
70 | passwordsAsPatterns.add(Pattern.compile(r));
71 | }
72 | }
73 |
74 | return passwordsAsPatterns;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/resources/com/michelin/cio/hudson/plugins/maskpasswords/MaskPasswordsBuildWrapper.properties:
--------------------------------------------------------------------------------
1 | # The MIT License
2 | #
3 | # Copyright (c) 2010-2011, Manufacture Francaise des Pneumatiques Michelin,
4 | # Romain Seguy
5 | #
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
7 | # of this software and associated documentation files (the "Software"), to deal
8 | # in the Software without restriction, including without limitation the rights
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | # copies of the Software, and to permit persons to whom the Software is
11 | # furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included in
14 | # all copies or substantial portions of the Software.
15 | #
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | # THE SOFTWARE.
23 |
24 | DisplayName=Mask passwords and regexes (and enable global passwords)
25 |
--------------------------------------------------------------------------------
/src/main/resources/com/michelin/cio/hudson/plugins/maskpasswords/MaskPasswordsBuildWrapper/config.jelly:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/main/resources/com/michelin/cio/hudson/plugins/maskpasswords/MaskPasswordsBuildWrapper/config.properties:
--------------------------------------------------------------------------------
1 | # The MIT License
2 | #
3 | # Copyright (c) 2011, Manufacture Francaise des Pneumatiques Michelin, Romain Seguy
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | # THE SOFTWARE.
22 |
23 | HowToConfigureThisPlugin=Password Parameters, or any other \
24 | type of build parameters or regexes selected for masking in Jenkins'' main \
25 | configuration screen (Manage Jenkins > Configure \
26 | System), will be automatically masked.
27 | PasswordPairTitle=Name/Password Pairs
28 | RegexTitle=Regular Expressions
29 |
--------------------------------------------------------------------------------
/src/main/resources/com/michelin/cio/hudson/plugins/maskpasswords/MaskPasswordsBuildWrapper/global.jelly:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/main/resources/com/michelin/cio/hudson/plugins/maskpasswords/MaskPasswordsBuildWrapper/global.properties:
--------------------------------------------------------------------------------
1 | # The MIT License
2 | #
3 | # Copyright (c) 2011, Manufacture Francaise des Pneumatiques Michelin, Romain Seguy
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | # THE SOFTWARE.
22 |
23 | GlobalVarPasswordPairs=Mask Passwords - Global name/password pairs
24 | ParameterDefinitions=Mask Passwords - Parameters to automatically mask
25 | GlobalVarMaskRegexes=Mask Passwords - Global Regexes
26 | Regex=Regex to automatically mask
27 | EnabledGlobally=Mask Passwords - Enable Globally
28 |
--------------------------------------------------------------------------------
/src/main/resources/com/michelin/cio/hudson/plugins/maskpasswords/MaskPasswordsBuildWrapper/help-globalVarMaskEnabledGlobally.html:
--------------------------------------------------------------------------------
1 |
2 | If checked, the a MaskPasswordsConsoleLogFilter will be injected into EVERY Build, regardless of the Project's settings.
3 | This is generally bad practice, but if your foremost concern is having secrets hidden, it may be worth trying. Note that
4 | this should be tested with your jobs before being relied on; it's possible that custom Job types will not honor this.
5 | Pipeline jobs are not currently supported, vote for JENKINS-30777)
6 | if you really need this functionality.
7 |
Define a list of regular expression patterns
27 | to be masked in build output. If the Mask passwords option is enabled on these
28 | jobs or the Mask Passwords - Enable Globally global option is enabled,
29 | then the defined regexes will be automatically masked from the console.
30 |
Blank values are not accepted. Patterns will be compiled via
31 | java.util.regex.Pattern.
32 | Regexes and patterns for defined passwords will be combined with pipes ("|")
33 | within one large parenthesized pattern to mask from output (i.e. the final pattern to mask
34 | for two passwords ("Password1" and "Password2") and two regexes ("Regex1" and "Regex2")
35 | will be: "(Password1|Password2|Regex1|Regex2)").
36 |
Define a list of Name/Password pairs to be used from jobs
27 | as build variables. If the Mask passwords option is enabled on these
28 | jobs or the Mask Passwords - Enable Globally global option is enabled,
29 | then the defined passwords will be automatically masked from the console.
30 |
Blank values are not accepted (for both key and password).
When enabled, allows masking passwords that may appear in the console.
28 |
Passwords or regexes to be masked can be defined at three levels.
29 |
First, it is possible to select in Jenkins configuration screen
30 | the build parameters whose value must be masked. For example, selecting the
31 | Password Parameter type is a good idea.
32 |
Second, still in Jenkins' configuration screen, it is possible
33 | to define passwords to be masked as global Name/Password
34 | pairs, or regexes to be masked.
35 |
Third, on a per job basis (that is, in the current configuration screen),
36 | passwords to be masked can be defined as local Name/Password
37 | pairs, or regexes can be masked:
38 |
Password is aimed at containing a password to be masked from the
39 | console. Empty and blank values are not allowed (e.g. " ",
40 | etc.). This field is ciphered. This field can not contain variables, they
41 | won't be expanded.
42 |
If Name is set, then a build variable will be defined accordingly.
43 | This allows accessing the password by using the variable rather than hard-coding
44 | it in a field which is not ciphered (e.g. the ones of the Invoke Ant or
45 | Execute shell build steps).
46 |
If Regex is set, then any text matching that regex will be masked
47 | in the console output. The regex should not contain start- or end-of-string
48 | markers and will be internally and-ed (piped) together with all other regexes
49 | and regexes matching the specified names and passwords.
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/main/resources/com/michelin/cio/hudson/plugins/passwordparam/PasswordParameterDefinition.properties:
--------------------------------------------------------------------------------
1 | # The MIT License
2 | #
3 | # Copyright (c) 2011, Manufacture Française des Pneumatiques Michelin, Romain Seguy
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | # THE SOFTWARE.
22 |
23 | DisplayName=Non-Stored Password Parameter
24 |
--------------------------------------------------------------------------------
/src/main/resources/com/michelin/cio/hudson/plugins/passwordparam/PasswordParameterDefinition/config.jelly:
--------------------------------------------------------------------------------
1 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/main/resources/com/michelin/cio/hudson/plugins/passwordparam/PasswordParameterDefinition/index.jelly:
--------------------------------------------------------------------------------
1 |
25 |
26 |
29 |
30 |
31 |