jenkinsci / configuration-as-code-plugin

Jenkins Configuration as Code Plugin
https://plugins.jenkins.io/configuration-as-code
MIT License
2.68k stars 717 forks source link

File credentials' secretBytes doesn't get replaced #884

Closed dev-dsp closed 5 years ago

dev-dsp commented 5 years ago

CWP config sample (there are lots of plugins there, all of them have latest version):

bundle:
  groupId: "com.devops.demo"
  artifactId: "some-ci"
  vendor: "DevOps"
  title: "Configuration-as-Code demo"
  description: "Configuration-as-Code demo, produced by Custom WAR Packager"
buildSettings:
  docker:
    base: "jenkins/jenkins:2.164.3"
    tag: "some-ci"
    build: true
war:
  groupId: "org.jenkins-ci.main"
  artifactId: "jenkins-war"
  source:
    version: 2.164.3

plugins:
  #
  # required
  #
  - groupId: "io.jenkins"
    artifactId: "configuration-as-code"
    source:
      version: "1.15"
  - groupId: "io.jenkins.configuration-as-code"
    artifactId: "configuration-as-code-support"
    source:
      version: "1.15"
...
  - groupId: org.jenkins-ci.plugins
    artifactId: plain-credentials
    source:
      version: '1.5'
...

casc:
  - id: "casc"
    source:
      dir: some-ci.conf.yml

CASC:

credentials:
  system:
    domainCredentials:
    - credentials: 
      - gitLabApiTokenImpl:
          apiToken: ${jenkins_apikey_gitlab}
          id: jenkins_apikey_gitlab
          scope: GLOBAL
      - file:
          id: cert_devopsjenkinsgke
          filename: k8s.crt
          secretBytes: ${cert_devopsjenkinsgke_b64}
          scope: GLOBAL
...
  clouds:
  - kubernetes:
      credentialsId: ${admin_creds_devopsjenkinsgke}
      serverCertificate: ${cert_devopsjenkinsgke}

I am using K8S secrets to provide credentials to CASC. It works fine for any field (mentioned some of them in config) except of the file credentials' secretBytes. Here is what I get in Jenkins startup logs:

May 16, 2019 5:58:07 PM io.jenkins.plugins.casc.impl.configurators.DataBoundConfigurator tryConstructor
INFO: Setting class org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl.secretBytes = ${cert_devopsjenkinsgke_b64}
May 16, 2019 5:58:07 PM jenkins.InitReactorRunner$1 onTaskFailed
SEVERE: Failed ConfigurationAsCode.init
java.lang.Error: java.lang.reflect.InvocationTargetException
        at hudson.init.TaskMethodFinder.invoke(TaskMethodFinder.java:110)
        at hudson.init.TaskMethodFinder$TaskImpl.run(TaskMethodFinder.java:175)
        at org.jvnet.hudson.reactor.Reactor.runTask(Reactor.java:296)
        at jenkins.model.Jenkins$5.runTask(Jenkins.java:1096)
        at org.jvnet.hudson.reactor.Reactor$2.run(Reactor.java:214)
        at org.jvnet.hudson.reactor.Reactor$Node.run(Reactor.java:117)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
...
Caused by: io.jenkins.plugins.casc.ConfiguratorException: credentials: error configuring 'credentials' with class io.jenkins.plugins.casc.support.credentials.CredentialsRootConfigurator configurator
...
Caused by: io.jenkins.plugins.casc.ConfiguratorException: file: Failed to construct instance of class org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl.
 Constructor: public org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl(com.cloudbees.plugins.credentials.CredentialsScope,java.lang.String,java.lang.String,org.apache.commons.fileupload.FileItem,java.lang.String,com.cloudbees.plugins.credentials.SecretBytes) throws java.io.IOException.
 Arguments: [com.cloudbees.plugins.credentials.CredentialsScope$2, java.lang.String, null, null, null, com.cloudbees.plugins.credentials.SecretBytes]
        at io.jenkins.plugins.casc.impl.configurators.DataBoundConfigurator.tryConstructor(DataBoundConfigurator.java:149)
...

So it is just not replaced for some reason. File representing K8S secret exists in container:

$ >>> kubectl -n k8s-jenkins exec -it jenkins-0 cat /secrets/cert_devopsjenkinsgke_b64
{LS0tLS1CRUdJTiBDRVJUSUZ ...

Spent some time investigating it, but with no luck. Could someone help me with that?

dev-dsp commented 5 years ago

Other related question is regarding secretBytes format. As I understand, it should be "{" + base64(file_contents) + "}"?

timja commented 5 years ago

here's an example: https://github.com/jenkinsci/plain-credentials-plugin/pull/14/files#diff-189b0e5434d71b3eb4bc77dda6974e0fR12

dev-dsp commented 5 years ago

I've just tried local build scripts with the same configuration. They build and run Docker image and CASC takes secrets from Vault. However, nothing changed and I get same stack trace.

jetersen commented 5 years ago

Could you pass the full stack trace? Clearly, incomplete stack traces is no good.

jetersen commented 5 years ago

could you try

- file:
    id: cert_devopsjenkinsgke
    fileName: k8s.crt
    description: "kubernetes cert"
    secretBytes: ${cert_devopsjenkinsgke_b64:-Tk9UX0FfU0VDUkVU}
    scope: GLOBAL

Tk9UX0FfU0VDUkVU base64 of NOT_A_SECRET

dev-dsp commented 5 years ago

Sure @casz, here is full startup log: https://pastebin.com/FxN6vAMR

jetersen commented 5 years ago

Read the stack trace is my pro tip:

Caused by: java.lang.NullPointerException
    at org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl.<init>(FileCredentialsImpl.java:157)
jetersen commented 5 years ago

~What version of plain credentials plugin are you using?~ In the description :man_facepalming:

dev-dsp commented 5 years ago

As listed above, it is 1.5

dev-dsp commented 5 years ago

I've tried your variant, but nothing changed:

INFO: Setting class org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl.secretBytes = ${cert_devopsjenkinsgke_b64:-Tk9UX0FfU0VDUkVU}
May 17, 2019 11:19:24 AM jenkins.InitReactorRunner$1 onTaskFailed
SEVERE: Failed ConfigurationAsCode.init
java.lang.Error: java.lang.reflect.InvocationTargetException
jetersen commented 5 years ago

https://github.com/jenkinsci/plain-credentials-plugin/blob/3878c2e41aaa120d7596e3aeecb94520c92d902e/src/main/java/org/jenkinsci/plugins/plaincredentials/impl/FileCredentialsImpl.java#L157

dev-dsp commented 5 years ago

Oh I see it now, should be fileName, not filename, let me verify

dev-dsp commented 5 years ago

Yep, now it works like a charm. I've read this line before creating issue here, but then I didn't find anything strange there :)

dev-dsp commented 5 years ago

@casz is it possible to implement case-insensitive setters or field existence verification on CASC side?

jetersen commented 5 years ago

We could write some complex logic however since Java allows this case:

public static void test(String fileName, String filename) {
    System.out.println("Hello World: " + fileName + " " + filename);
}

I see no reason to maintain logic that checks if parameters with case insensitivity and whether one or more parameters conflict with the naming.

Since someone can do this

public static void test(String fileName, String filename, String FILENAME, String fiLENAME, String fileNAME) {
    // CRY
}
dev-dsp commented 5 years ago

Ok, case-insensitivity may be not a good choice here. I just would like CASC to be more clear with such errors, if it is possible. Of course, it is just misconfiguration and not related to CASC itself, but such failures may require more time for investigation. Anyway, thanks for your quick help!

jetersen commented 5 years ago

We could properly do something better like returning the expected parameters

jetersen commented 4 years ago

FYI might be helpful to know that JCasC is getting native support for variable expansion with base64 and file read. See #1408