terraform-routeros / terraform-provider-routeros

Terraform Provider for Mikrotik RouterOS
Mozilla Public License 2.0
165 stars 46 forks source link

Import system_certificate with sign block #463

Closed OJFord closed 1 week ago

OJFord commented 1 month ago

What documentation needs updating?: I don't follow this instruction, in the import section of routeros_system_certificate:

#If you plan to manipulate the certificate requiring signing, you need to correctly fill in the sign{} section.
#Changes in the sign{} section will not cause changes in the certificate. It's not a bug, it's a feature!

What needs to be added to the documentation?: I had two to import, a CA with config sign{} and one for www-ssl with sign{ ca = "ca" } (the name of the CA cert).

They imported fine, but then on plan they have the diff:

      + sign { # forces replacement
          + ca          = (known after apply)
          + ca_crl_host = (known after apply)
        }

and:

      + sign { # forces replacement
          + ca          = "ca"
          + ca_crl_host = (known after apply)
        }

respectively.

If I comment out the sign block, they show no diff. (And that does seem incorrect, they're presently self-signed & signed by CA respectively, so in absence of that block surely they should be for recreation since that would suggest I want them unsigned?)

I have tried specifying ca_crl_host as null explicitly, "", and tostring(null) (which is what is shown in terraform console as the present value); all of which give the same behaviour.

I'm not sure exactly what needs to be added/changed here, because I don't understand it, and it seems like that existing comment is supposed to address it.

Cheers.

vaerh commented 1 month ago

Greetings! Do I understand correctly that you are trying to use the latest added functionality of importing certificates? If so, what is the purpose of importing a certificate with a signing block? Or better yet, please post a part of the resource configuration (without the actual certificates) so I can reproduce the problem.

OJFord commented 1 month ago

Hi, no I mean importing in the sense of terraform importing certs created in WebFig to tf state.

i.e.

  1. System > Certificates > Add New
    • name: ca
    • key usage: crl sign, key sign
    • self-sign it
  2. Terminal > /certificate get [print show-ids] to get the ID
  3. terraform config:

    resource "routeros_system_certificate" "ca" {
    name = "ca"
    
    key_usage = [
    "crl-sign",
    "key-cert-sign",
    ]
    
    days_valid = 365
    
    key_size = "secp521r1"
    
    trusted = true
    sign {}
    }
  4. terraform import routeros_system_certificate.ca ID (works)
  5. terraform plan (observe diff)

Similarly for the one signed by this one (rather than self-signed) as described above.

vaerh commented 1 month ago
This works for me

![image](https://github.com/terraform-routeros/terraform-provider-routeros/assets/64400271/ac16f6c5-bf55-46a4-ab87-274832276a7f) ``` hcl resource "routeros_system_certificate" "ca" { name = "ca" common_name = "ca" key_usage = [ "crl-sign", "key-cert-sign", ] days_valid = 365 key_size = "secp521r1" trusted = true } terraform import routeros_system_certificate.ca *A ``` ![image](https://github.com/terraform-routeros/terraform-provider-routeros/assets/64400271/f6b76611-c827-433e-98e0-edec94da1436) ``` hcl terraform plan ``` ![image](https://github.com/terraform-routeros/terraform-provider-routeros/assets/64400271/c24cafa2-aab9-4a05-b4fe-a78762e127ee) ``` hcl resource "routeros_system_certificate" "ca" { name = "ca" common_name = "ca" key_usage = [ "crl-sign", "key-cert-sign", ] days_valid = 365 key_size = "secp521r1" trusted = true } resource "routeros_system_certificate" "services" { name = "www" common_name = "www" key_size = "secp521r1" sign { ca = "ca" } } terraform apply ``` ![image](https://github.com/terraform-routeros/terraform-provider-routeros/assets/64400271/273f6285-f52f-4520-8c99-89f7212e51da)

In fact, the sign {} block adds an additional signing action after certificate creation. That is, the provider sends 2 requests: the first add and the second sign. The above example imports an existing CA and creates a new certificate with its signing. By analogy (without the sign block) you can import any previously signed certificate. Please do not consider it difficult to clarify the question if I have misunderstood your problem again.

OJFord commented 1 month ago

In your router_system_certificate.ca, since it doesn't have a sign {} block I would expect that this is the config for an unsigned cert; so it should see that the imported certificate is signed and want to replace it?

In your router_system_certificate.services, if instead of letting terraform apply create it you create it manually (sign with ca = ca) and then import it, the sign { ca = "ca" } will force replacement, even though it matches (as far as I can tell) what you did.

In either case it seems the only way to import (and not replace) device-created certs is to leave the sign block out of the configuration. This works, but means there's no record of that, and if the certs were to be deleted (or have a forced replacement due to some other change) then the terraform config would cause them to be recreated incorrectly.

vaerh commented 1 month ago

Yes, as I mentioned earlier the sign {} block is a trigger. If it is present in the resource configuration, the provider tries to sign the certificate. Strictly speaking, what MT considers a certificate is actually a signing request (CSR), since a certificate is an object that has an issuer. For this reason, all valid certificates should be imported without specifying the sign {} block, because they have already been signed. Yes, you are correct: changes to the attributes of the certificate will cause the certificate to be recreated, since there is no other way to change the contents of the certificate. I will try to write a small explanation about the sign {} block in the next releases so that there is no confusion about its use.

OJFord commented 1 month ago

changes to the attributes of the certificate will cause the certificate to be recreated, since there is no other way to change the contents of the certificate.

Yes, but the problem is when that happens it will not have any signature that the imported original may have had; so it will be different in more ways than the intended attribute change.

Maybe signing needs to be a separate resource to track appropriately?

vaerh commented 1 month ago

If you have some nice solution to separate the signing and the certificate itself, I'd be glad! This general problem of additional actions over resources. It is not a specific problem of certificate creation. I already wrote to one of the users in the discussion that in the TF concept the action cannot be detached from the resource. And signing action is just a non-self-sufficient action. That's the main problem. From the MT point of view, the result of signing action is a small attribute that says that this action was performed. And that's it.

Once again I would like to clarify the concept that is currently implemented: if you have a valid certificate (not CSR), you import it, no matter to or from the router (see #448). But if we need to create a new certificate within the PKI or change the attributes of an existing one, then the only option is CRS + sign = CRT.

OJFord commented 1 month ago

I've just tried out the recently added import block (i.e. importing in the other direction!) for routeros_system_certificate, and there seems to be a similar issue there: once you apply it, it sees all the imported contents as a diff and wants to replace it.

It's also slightly obscured by the error:

╷
│ Error: POST 'https://192.168.88.1/rest/certificate/import' returned response code: 400, message: 'Bad Request', details: 'ambiguous value of file-name, more than one possible value matches input'
│

which it reports despite as far as I can tell importing it successfully. That's with a cert_file_name = routeros_file.blah.name, as uploaded, and as shown in WebFig in the import dropdown box, so it seems correct (and unambiguous).

vaerh commented 1 month ago

The provider will try to import the certificate first and then report that there are two certificates with the same name on the MT. You can see more details here. When I implemented the logic, the starting point was an MT without any certificates. In principle we can make changes, but we need to clearly understand what scenario we are implementing. By the way, to avoid inconsistency, I would delete the resource of a certain certificate before importing it from the state file. I tested and debugged the import in this way and I would not want to complicate the logic, as there are already a lot of problems with different user scenarios.